@melandlabs/memory-store 0.3.0 → 1.0.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/README.md +209 -56
- package/dist/{config-2Q092MUz.d.ts → config-CnbqSek5.d.ts} +215 -2
- package/dist/contracts.d.ts +1 -0
- package/dist/contracts.js +2 -0
- package/dist/http.d.ts +21 -5
- package/dist/http.js +1051 -155
- package/dist/index.d.ts +230 -15
- package/dist/index.js +1087 -152
- package/dist/mcp.d.ts +14 -5
- package/dist/mcp.js +1188 -174
- package/dist/policies/memory-graph-correction-policy.d.ts +1 -0
- package/dist/policies/memory-graph-correction-policy.js +4 -0
- package/dist/policies/memory-graph-write-policy.d.ts +1 -0
- package/dist/policies/memory-graph-write-policy.js +2 -0
- package/dist/search/unified-search.d.ts +6 -19
- package/dist/search/unified-search.js +749 -145
- package/dist/server/cli-http.js +1051 -155
- package/dist/server/cli-mcp.js +1185 -171
- package/dist/server/cli-shared.d.ts +5 -2
- package/dist/server/cli-shared.js +6 -4
- package/dist/storage/chroma-memory-index.d.ts +1 -0
- package/dist/storage/chroma-memory-index.js +1 -2
- package/dist/storage/postgres-raw-message-factory.d.ts +4 -1
- package/dist/storage/raw-message-store.d.ts +5 -2
- package/dist/storage/raw-message-store.js +6 -4
- package/dist/storage/sqlite-raw-message-store.d.ts +5 -1
- package/dist/storage/sqlite-raw-message-store.js +6 -4
- package/dist/storage/sqlite-vector-index.d.ts +4 -1
- package/dist/storage/sqlite-vector-index.js +4 -4
- package/dist/unified-search-Bft57TRH.d.ts +122 -0
- package/package.json +11 -7
package/dist/http.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { vsaNormalizeVector, vsaAssertSameDim } from '@melandlabs/contracts';
|
|
1
2
|
import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
|
|
2
3
|
import { ChromaVectorStore } from '@melandlabs/rag';
|
|
3
4
|
import { mkdirSync } from 'fs';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
import { getOpenContextPath } from '@melandlabs/env-config';
|
|
7
|
+
import { SQLiteRawMessageManager, getSQLiteVsaStore, closeSQLiteVsaStore } from '@melandlabs/sqlite';
|
|
7
8
|
import { serve } from '@hono/node-server';
|
|
8
9
|
import { Hono } from 'hono';
|
|
10
|
+
import { applyReflectedConsolidationPlan } from '@melandlabs/memory-consolidation';
|
|
11
|
+
import { superpose, bind, unbind, cleanup, cosineSimilarity } from '@melandlabs/vsa';
|
|
9
12
|
|
|
10
13
|
var __defProp = Object.defineProperty;
|
|
11
14
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -16,8 +19,6 @@ var __export = (target, all) => {
|
|
|
16
19
|
for (var name in all)
|
|
17
20
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
21
|
};
|
|
19
|
-
|
|
20
|
-
// src/contracts.ts
|
|
21
22
|
function normalizeTimestampToMs(value) {
|
|
22
23
|
if (!Number.isFinite(value)) {
|
|
23
24
|
return 0;
|
|
@@ -186,7 +187,7 @@ __export(sqlite_raw_message_store_exports, {
|
|
|
186
187
|
function resolveSQLiteRawMessageDbPath(dbPath) {
|
|
187
188
|
if (dbPath && dbPath.length > 0) return dbPath;
|
|
188
189
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
189
|
-
return fromEnv && fromEnv.length > 0 ? fromEnv :
|
|
190
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : getOpenContextPath("memory", "store.db");
|
|
190
191
|
}
|
|
191
192
|
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
192
193
|
return true;
|
|
@@ -220,12 +221,11 @@ async function lexicalSearchRawMessages(input) {
|
|
|
220
221
|
}
|
|
221
222
|
return mgr.lexicalSearchMessages(input);
|
|
222
223
|
}
|
|
223
|
-
var manager
|
|
224
|
+
var manager;
|
|
224
225
|
var init_sqlite_raw_message_store = __esm({
|
|
225
226
|
"src/storage/sqlite-raw-message-store.ts"() {
|
|
226
227
|
init_chroma_memory_index();
|
|
227
228
|
manager = null;
|
|
228
|
-
DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
|
|
229
229
|
}
|
|
230
230
|
});
|
|
231
231
|
|
|
@@ -325,6 +325,41 @@ var init_raw_message_store = __esm({
|
|
|
325
325
|
init_chroma_memory_index();
|
|
326
326
|
init_raw_message_store();
|
|
327
327
|
|
|
328
|
+
// src/search/reranker.ts
|
|
329
|
+
async function applyReranker(reranker, query, results) {
|
|
330
|
+
if (!reranker || results.length === 0) {
|
|
331
|
+
return results;
|
|
332
|
+
}
|
|
333
|
+
const scores = await reranker.rerank({
|
|
334
|
+
query,
|
|
335
|
+
candidates: results.map((result) => ({
|
|
336
|
+
id: result.id,
|
|
337
|
+
content: result.content,
|
|
338
|
+
metadata: result.metadata
|
|
339
|
+
}))
|
|
340
|
+
});
|
|
341
|
+
if (scores.length === 0) {
|
|
342
|
+
return results;
|
|
343
|
+
}
|
|
344
|
+
const byId = new Map(results.map((result) => [result.id, result]));
|
|
345
|
+
const reordered = [];
|
|
346
|
+
const seen = /* @__PURE__ */ new Set();
|
|
347
|
+
for (const { id, score } of scores) {
|
|
348
|
+
const hit = byId.get(id);
|
|
349
|
+
if (!hit || seen.has(id)) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
seen.add(id);
|
|
353
|
+
reordered.push({ ...hit, metadata: { ...hit.metadata, rerankerScore: score } });
|
|
354
|
+
}
|
|
355
|
+
for (const hit of results) {
|
|
356
|
+
if (!seen.has(hit.id)) {
|
|
357
|
+
reordered.push(hit);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return reordered;
|
|
361
|
+
}
|
|
362
|
+
|
|
328
363
|
// src/search/utilities.ts
|
|
329
364
|
var DEFAULT_LIMIT = 10;
|
|
330
365
|
var DEFAULT_THRESHOLD = 0.7;
|
|
@@ -446,6 +481,264 @@ function isRawMemorySemanticResult(result) {
|
|
|
446
481
|
const item = result;
|
|
447
482
|
return typeof item.id === "string" && typeof item.content === "string" && typeof item.similarity === "number" && Boolean(item.metadata) && typeof item.metadata === "object";
|
|
448
483
|
}
|
|
484
|
+
async function resolveScopePeer(input) {
|
|
485
|
+
const peers = input.peerFilter ?? [];
|
|
486
|
+
if (peers.length === 0) {
|
|
487
|
+
return { peers: [], warnings: [] };
|
|
488
|
+
}
|
|
489
|
+
if (typeof input.scopeCheck !== "function") {
|
|
490
|
+
return { peers, warnings: [] };
|
|
491
|
+
}
|
|
492
|
+
const retained = [];
|
|
493
|
+
const dropped = [];
|
|
494
|
+
for (const peer of peers) {
|
|
495
|
+
const ok = await input.scopeCheck({ userId: input.userId, peers: [peer] });
|
|
496
|
+
if (ok) {
|
|
497
|
+
retained.push(peer);
|
|
498
|
+
} else {
|
|
499
|
+
dropped.push(peer);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (dropped.length === 0) {
|
|
503
|
+
return { peers, warnings: [] };
|
|
504
|
+
}
|
|
505
|
+
if (retained.length === 0) {
|
|
506
|
+
return {
|
|
507
|
+
peers: [],
|
|
508
|
+
warnings: [
|
|
509
|
+
{
|
|
510
|
+
source: "memory",
|
|
511
|
+
code: "peer_filter_outside_user_scope",
|
|
512
|
+
message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored; falling back to userId scope`
|
|
513
|
+
}
|
|
514
|
+
]
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
peers: retained,
|
|
519
|
+
warnings: [
|
|
520
|
+
{
|
|
521
|
+
source: "memory",
|
|
522
|
+
code: "peer_filter_outside_user_scope",
|
|
523
|
+
message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored because they fall outside userId ${input.userId}`
|
|
524
|
+
}
|
|
525
|
+
]
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/search/gather-evidence.ts
|
|
530
|
+
var DEFAULT_TIERS = ["summary", "raw", "insight", "knowledge"];
|
|
531
|
+
var MAX_EVIDENCE_CHARS = 600;
|
|
532
|
+
function toSummaryResult(row) {
|
|
533
|
+
const start = typeof row.startTimestamp === "number" ? row.startTimestamp : void 0;
|
|
534
|
+
const end = typeof row.endTimestamp === "number" ? row.endTimestamp : void 0;
|
|
535
|
+
return {
|
|
536
|
+
type: "memory",
|
|
537
|
+
id: row.summaryId,
|
|
538
|
+
content: row.summaryText,
|
|
539
|
+
similarity: 1,
|
|
540
|
+
metadata: {
|
|
541
|
+
tier: "summary",
|
|
542
|
+
summaryTier: row.summaryTier,
|
|
543
|
+
keywords: row.keywords,
|
|
544
|
+
startTimestamp: start,
|
|
545
|
+
endTimestamp: end
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers) {
|
|
550
|
+
if (typeof deps.searchSummaries !== "function") {
|
|
551
|
+
return { hits: [], warnings: [] };
|
|
552
|
+
}
|
|
553
|
+
const keywords = input.query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
554
|
+
try {
|
|
555
|
+
const rows = await deps.searchSummaries({
|
|
556
|
+
userId: input.userId,
|
|
557
|
+
query: input.query,
|
|
558
|
+
keywords,
|
|
559
|
+
limit,
|
|
560
|
+
threshold,
|
|
561
|
+
authToken: input.authToken,
|
|
562
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
563
|
+
});
|
|
564
|
+
const hits = rows.map(toSummaryResult);
|
|
565
|
+
return { hits, warnings: [] };
|
|
566
|
+
} catch (error) {
|
|
567
|
+
logger.warn?.("[memory-store] gather-evidence: summary search failed:", error);
|
|
568
|
+
return {
|
|
569
|
+
hits: [],
|
|
570
|
+
warnings: [
|
|
571
|
+
{
|
|
572
|
+
source: "memory",
|
|
573
|
+
code: "reflect_summaries_failed",
|
|
574
|
+
message: error.message ?? "reflect_summaries_failed"
|
|
575
|
+
}
|
|
576
|
+
]
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function truncate(input, max = MAX_EVIDENCE_CHARS) {
|
|
581
|
+
if (input.length <= max) {
|
|
582
|
+
return input;
|
|
583
|
+
}
|
|
584
|
+
return `${input.slice(0, max - 1)}\u2026`;
|
|
585
|
+
}
|
|
586
|
+
function buildSynthesisPrompt(input) {
|
|
587
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
588
|
+
for (const item of input.evidence) {
|
|
589
|
+
const bucket = grouped.get(item.source) ?? [];
|
|
590
|
+
bucket.push(item);
|
|
591
|
+
grouped.set(item.source, bucket);
|
|
592
|
+
}
|
|
593
|
+
const sections = [];
|
|
594
|
+
for (const tier of DEFAULT_TIERS) {
|
|
595
|
+
const items = grouped.get(tier) ?? [];
|
|
596
|
+
if (items.length === 0) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const lines = items.map(
|
|
600
|
+
(item, index) => ` [${index + 1}] (${item.id}, score=${item.score.toFixed(4)}) ${item.snippet}`
|
|
601
|
+
);
|
|
602
|
+
sections.push(`## ${tier}
|
|
603
|
+
${lines.join("\n")}`);
|
|
604
|
+
}
|
|
605
|
+
const schemaDirective = input.responseSchema ? `
|
|
606
|
+
Respond with a JSON object that conforms to the following schema:
|
|
607
|
+
${JSON.stringify(input.responseSchema, null, 2)}
|
|
608
|
+
Wrap your JSON in a single fenced \`\`\`json code block; do not add any prose outside the code block.` : "\nRespond in plain prose. Be concise; cite evidence by its [n] number.";
|
|
609
|
+
if (sections.length === 0) {
|
|
610
|
+
return `You are a reflection assistant.
|
|
611
|
+
|
|
612
|
+
Question: ${input.query}
|
|
613
|
+
|
|
614
|
+
No evidence was found across any tier. State that explicitly.${schemaDirective}`;
|
|
615
|
+
}
|
|
616
|
+
return `You are a reflection assistant. Synthesise the user's question using only the evidence below. Do not invent facts.
|
|
617
|
+
|
|
618
|
+
Question: ${input.query}
|
|
619
|
+
|
|
620
|
+
Evidence (grouped by tier):
|
|
621
|
+
${sections.join("\n\n")}${schemaDirective}`;
|
|
622
|
+
}
|
|
623
|
+
function extractJsonPayload(text) {
|
|
624
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
625
|
+
if (fenced?.[1]) {
|
|
626
|
+
return fenced[1];
|
|
627
|
+
}
|
|
628
|
+
const start = text.indexOf("{");
|
|
629
|
+
if (start === -1) {
|
|
630
|
+
return void 0;
|
|
631
|
+
}
|
|
632
|
+
let depth = 0;
|
|
633
|
+
let inString = false;
|
|
634
|
+
let escaped = false;
|
|
635
|
+
for (let i = start; i < text.length; i += 1) {
|
|
636
|
+
const ch = text[i];
|
|
637
|
+
if (escaped) {
|
|
638
|
+
escaped = false;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
if (inString) {
|
|
642
|
+
if (ch === "\\") {
|
|
643
|
+
escaped = true;
|
|
644
|
+
} else if (ch === '"') {
|
|
645
|
+
inString = false;
|
|
646
|
+
}
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (ch === '"') {
|
|
650
|
+
inString = true;
|
|
651
|
+
} else if (ch === "{") {
|
|
652
|
+
depth += 1;
|
|
653
|
+
} else if (ch === "}") {
|
|
654
|
+
depth -= 1;
|
|
655
|
+
if (depth === 0) {
|
|
656
|
+
return text.slice(start, i + 1);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return void 0;
|
|
661
|
+
}
|
|
662
|
+
function coerceAnswer(text, schema) {
|
|
663
|
+
if (!schema) {
|
|
664
|
+
return text;
|
|
665
|
+
}
|
|
666
|
+
const payload = extractJsonPayload(text);
|
|
667
|
+
if (!payload) {
|
|
668
|
+
return text;
|
|
669
|
+
}
|
|
670
|
+
try {
|
|
671
|
+
const parsed = JSON.parse(payload);
|
|
672
|
+
if (parsed && typeof parsed === "object") {
|
|
673
|
+
const answer = parsed.answer;
|
|
674
|
+
if (typeof answer === "string") {
|
|
675
|
+
return answer;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
} catch {
|
|
679
|
+
}
|
|
680
|
+
return text;
|
|
681
|
+
}
|
|
682
|
+
async function synthesizeAnswer(input) {
|
|
683
|
+
const { query, hits, responseSchema, deps, logger } = input;
|
|
684
|
+
const evidence = hits.map((hit) => ({
|
|
685
|
+
id: hit.id,
|
|
686
|
+
source: mapHitToTier(hit),
|
|
687
|
+
snippet: truncate(hit.content),
|
|
688
|
+
score: hit.similarity
|
|
689
|
+
}));
|
|
690
|
+
const warnings = [];
|
|
691
|
+
const complete = deps.reasoning?.complete;
|
|
692
|
+
if (!complete) {
|
|
693
|
+
warnings.push({
|
|
694
|
+
source: "memory",
|
|
695
|
+
code: "reflect_llm_not_configured",
|
|
696
|
+
message: "No `reasoning.complete` callback is configured; search() returned evidence without an LLM synthesis."
|
|
697
|
+
});
|
|
698
|
+
return { answer: "", warnings };
|
|
699
|
+
}
|
|
700
|
+
const prompt = buildSynthesisPrompt({ query, evidence, responseSchema });
|
|
701
|
+
try {
|
|
702
|
+
const raw = await complete(prompt);
|
|
703
|
+
const answer = coerceAnswer(raw, responseSchema);
|
|
704
|
+
logger.log?.("[memory-store] search synthesis completed", {
|
|
705
|
+
evidenceCount: evidence.length,
|
|
706
|
+
answerChars: answer.length
|
|
707
|
+
});
|
|
708
|
+
return { answer, warnings };
|
|
709
|
+
} catch (error) {
|
|
710
|
+
logger.warn?.("[memory-store] search synthesis failed:", error);
|
|
711
|
+
warnings.push({
|
|
712
|
+
source: "memory",
|
|
713
|
+
code: "reflect_llm_failed",
|
|
714
|
+
message: error.message ?? "reflect_llm_failed"
|
|
715
|
+
});
|
|
716
|
+
return { answer: "", warnings };
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function mapHitToTier(hit) {
|
|
720
|
+
const tierMarker = hit.metadata?.tier;
|
|
721
|
+
if (tierMarker === "summary") {
|
|
722
|
+
return "summary";
|
|
723
|
+
}
|
|
724
|
+
if (hit.type === "memory") {
|
|
725
|
+
return "raw";
|
|
726
|
+
}
|
|
727
|
+
if (hit.type === "insight") {
|
|
728
|
+
return "insight";
|
|
729
|
+
}
|
|
730
|
+
if (hit.type === "knowledge") {
|
|
731
|
+
return "knowledge";
|
|
732
|
+
}
|
|
733
|
+
return hit.type;
|
|
734
|
+
}
|
|
735
|
+
async function resolveSearchScopePeers(input) {
|
|
736
|
+
return resolveScopePeer({
|
|
737
|
+
userId: input.userId,
|
|
738
|
+
peerFilter: input.peerFilter,
|
|
739
|
+
scopeCheck: input.deps.peerScopeCheck
|
|
740
|
+
});
|
|
741
|
+
}
|
|
449
742
|
|
|
450
743
|
// src/search/unified-search.ts
|
|
451
744
|
function deriveLexicalKeywords(query) {
|
|
@@ -517,7 +810,7 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
517
810
|
authToken: input.authToken
|
|
518
811
|
});
|
|
519
812
|
}
|
|
520
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
813
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers = []) {
|
|
521
814
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
522
815
|
let semantic = [];
|
|
523
816
|
if (isRawMessageChromaEnabled()) {
|
|
@@ -547,6 +840,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
547
840
|
}
|
|
548
841
|
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
549
842
|
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
843
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
550
844
|
semantic = (await Promise.all(
|
|
551
845
|
filters.map(
|
|
552
846
|
(filter) => searchRawMessagesAnn({
|
|
@@ -554,7 +848,9 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
554
848
|
queryEmbedding,
|
|
555
849
|
limit,
|
|
556
850
|
threshold,
|
|
557
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
851
|
+
botId: "botId" in filter ? filter.botId : void 0,
|
|
852
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
853
|
+
...factTypes ? { factTypes } : {}
|
|
558
854
|
})
|
|
559
855
|
)
|
|
560
856
|
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
@@ -564,11 +860,14 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
564
860
|
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
565
861
|
const manager2 = await getRawMessageManager2();
|
|
566
862
|
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
863
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
567
864
|
const results = await manager2.searchMessagesSemantically({
|
|
568
865
|
userId: input.userId,
|
|
569
866
|
queryEmbedding,
|
|
570
867
|
limit,
|
|
571
|
-
threshold
|
|
868
|
+
threshold,
|
|
869
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
870
|
+
...factTypes ? { factTypes } : {}
|
|
572
871
|
});
|
|
573
872
|
semantic = results.map((r) => ({
|
|
574
873
|
type: "memory",
|
|
@@ -604,7 +903,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
604
903
|
metadata: hit.metadata
|
|
605
904
|
};
|
|
606
905
|
}
|
|
607
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
906
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers = []) {
|
|
608
907
|
if (keywords.length === 0) {
|
|
609
908
|
return [];
|
|
610
909
|
}
|
|
@@ -612,13 +911,16 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
612
911
|
try {
|
|
613
912
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
614
913
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
914
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
615
915
|
return (await Promise.all(
|
|
616
916
|
filters.map(
|
|
617
917
|
(botId) => searchRawMessagesLexical({
|
|
618
918
|
userId: input.userId,
|
|
619
919
|
keywords,
|
|
620
920
|
limit: Math.ceil(limit / filters.length),
|
|
621
|
-
botId
|
|
921
|
+
botId,
|
|
922
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
923
|
+
...factTypes ? { factTypes } : {}
|
|
622
924
|
})
|
|
623
925
|
)
|
|
624
926
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -632,13 +934,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
632
934
|
try {
|
|
633
935
|
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
634
936
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
937
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
635
938
|
return (await Promise.all(
|
|
636
939
|
filters.map(
|
|
637
940
|
(botId) => lexicalSearchRawMessages2({
|
|
638
941
|
userId: input.userId,
|
|
639
942
|
keywords,
|
|
640
943
|
limit: Math.ceil(limit / filters.length),
|
|
641
|
-
botId
|
|
944
|
+
botId,
|
|
945
|
+
...factTypes ? { factTypes } : {}
|
|
642
946
|
})
|
|
643
947
|
)
|
|
644
948
|
)).flat().filter(Boolean).map(
|
|
@@ -651,6 +955,42 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
651
955
|
}
|
|
652
956
|
return [];
|
|
653
957
|
}
|
|
958
|
+
function searchInputToUnified(input) {
|
|
959
|
+
return {
|
|
960
|
+
userId: input.userId,
|
|
961
|
+
query: input.query,
|
|
962
|
+
sources: input.sources ? [...input.sources] : void 0,
|
|
963
|
+
limit: input.limit,
|
|
964
|
+
threshold: input.threshold,
|
|
965
|
+
authToken: input.authToken,
|
|
966
|
+
includeArchivedInsights: input.includeArchivedInsights,
|
|
967
|
+
botIds: input.botIds,
|
|
968
|
+
documentIds: input.documentIds,
|
|
969
|
+
asOf: input.asOf,
|
|
970
|
+
dateFrom: input.dateFrom,
|
|
971
|
+
dateTo: input.dateTo,
|
|
972
|
+
mergeStrategy: input.mergeStrategy,
|
|
973
|
+
peerFilter: input.peerFilter,
|
|
974
|
+
reasoningStrategy: input.reasoningStrategy,
|
|
975
|
+
factTypes: input.factTypes
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
function mapHitToEvidenceSource(hit) {
|
|
979
|
+
const tierMarker = hit.metadata?.tier;
|
|
980
|
+
if (tierMarker === "summary") {
|
|
981
|
+
return "summary";
|
|
982
|
+
}
|
|
983
|
+
if (hit.type === "memory") {
|
|
984
|
+
return "raw";
|
|
985
|
+
}
|
|
986
|
+
if (hit.type === "insight") {
|
|
987
|
+
return "insight";
|
|
988
|
+
}
|
|
989
|
+
if (hit.type === "knowledge") {
|
|
990
|
+
return "knowledge";
|
|
991
|
+
}
|
|
992
|
+
return hit.type;
|
|
993
|
+
}
|
|
654
994
|
function createUnifiedSearch(deps = {}) {
|
|
655
995
|
const logger = deps.logger ?? console;
|
|
656
996
|
function resolveReasoningStrategy(input) {
|
|
@@ -660,130 +1000,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
660
1000
|
}
|
|
661
1001
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
662
1002
|
}
|
|
663
|
-
async function
|
|
664
|
-
const query = input.query.trim();
|
|
665
|
-
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
666
|
-
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
667
|
-
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
668
|
-
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
669
|
-
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
670
|
-
const warnings = [];
|
|
671
|
-
if (!query) {
|
|
672
|
-
return {
|
|
673
|
-
query,
|
|
674
|
-
sources,
|
|
675
|
-
results: [],
|
|
676
|
-
count: 0,
|
|
677
|
-
warnings
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
let memorySubs;
|
|
681
|
-
const reasoningInfo = { strategy: reasoningStrategy };
|
|
682
|
-
if (input.dateFrom || input.dateTo) {
|
|
683
|
-
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
684
|
-
}
|
|
685
|
-
if (sources.includes("memory")) {
|
|
686
|
-
if (isRawMessageStorageAvailable()) {
|
|
687
|
-
try {
|
|
688
|
-
memorySubs = await runMemorySource(
|
|
689
|
-
input,
|
|
690
|
-
limit,
|
|
691
|
-
threshold,
|
|
692
|
-
warnings,
|
|
693
|
-
reasoningStrategy,
|
|
694
|
-
reasoningInfo
|
|
695
|
-
);
|
|
696
|
-
} catch (error) {
|
|
697
|
-
logger.warn?.("[memory-store] memory source failed:", error);
|
|
698
|
-
warnings.push({
|
|
699
|
-
source: "memory",
|
|
700
|
-
code: "memory_search_failed",
|
|
701
|
-
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
} else {
|
|
705
|
-
warnings.push({
|
|
706
|
-
source: "memory",
|
|
707
|
-
code: "raw_message_storage_unavailable",
|
|
708
|
-
message: "Raw memory storage is not available in this environment."
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
const insightHits = [];
|
|
713
|
-
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
714
|
-
try {
|
|
715
|
-
const hits = await deps.searchInsights({
|
|
716
|
-
userId: input.userId,
|
|
717
|
-
query: input.query,
|
|
718
|
-
limit,
|
|
719
|
-
threshold,
|
|
720
|
-
botIds: input.botIds,
|
|
721
|
-
includeArchived: input.includeArchivedInsights,
|
|
722
|
-
authToken: input.authToken
|
|
723
|
-
});
|
|
724
|
-
for (const hit of hits) {
|
|
725
|
-
insightHits.push({
|
|
726
|
-
type: "insight",
|
|
727
|
-
id: hit.id,
|
|
728
|
-
content: hit.content,
|
|
729
|
-
similarity: hit.similarity,
|
|
730
|
-
metadata: hit.metadata
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
} catch (error) {
|
|
734
|
-
logger.warn?.("[memory-store] insights source failed:", error);
|
|
735
|
-
warnings.push({
|
|
736
|
-
source: "insights",
|
|
737
|
-
code: "insights_search_failed",
|
|
738
|
-
message: error.message ?? "insights_search_failed"
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
} else if (sources.includes("insights")) ;
|
|
742
|
-
const knowledgeHits = [];
|
|
743
|
-
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
744
|
-
try {
|
|
745
|
-
const hits = await deps.searchKnowledge({
|
|
746
|
-
userId: input.userId,
|
|
747
|
-
query: input.query,
|
|
748
|
-
options: {
|
|
749
|
-
limit,
|
|
750
|
-
threshold,
|
|
751
|
-
documentIds: input.documentIds
|
|
752
|
-
},
|
|
753
|
-
authToken: input.authToken
|
|
754
|
-
});
|
|
755
|
-
for (const hit of hits) {
|
|
756
|
-
knowledgeHits.push(toKnowledgeResult(hit));
|
|
757
|
-
}
|
|
758
|
-
} catch (error) {
|
|
759
|
-
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
760
|
-
warnings.push({
|
|
761
|
-
source: "knowledge",
|
|
762
|
-
code: "knowledge_search_failed",
|
|
763
|
-
message: error.message ?? "knowledge_search_failed"
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
} else if (sources.includes("knowledge")) ;
|
|
767
|
-
const merged = mergeAcrossSources({
|
|
768
|
-
memorySubs,
|
|
769
|
-
insightHits,
|
|
770
|
-
knowledgeHits,
|
|
771
|
-
limit,
|
|
772
|
-
strategy: mergeStrategy
|
|
773
|
-
});
|
|
774
|
-
const output = {
|
|
775
|
-
query: input.query,
|
|
776
|
-
sources,
|
|
777
|
-
results: merged,
|
|
778
|
-
count: merged.length,
|
|
779
|
-
warnings
|
|
780
|
-
};
|
|
781
|
-
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
782
|
-
output.reasoning = reasoningInfo;
|
|
783
|
-
}
|
|
784
|
-
return output;
|
|
785
|
-
}
|
|
786
|
-
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
1003
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
|
|
787
1004
|
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
788
1005
|
warnings.push({
|
|
789
1006
|
source: "memory",
|
|
@@ -809,7 +1026,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
809
1026
|
executor: {
|
|
810
1027
|
search: async (request) => {
|
|
811
1028
|
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
812
|
-
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1029
|
+
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1030
|
+
deps,
|
|
1031
|
+
input,
|
|
1032
|
+
keywords2,
|
|
1033
|
+
limit,
|
|
1034
|
+
logger,
|
|
1035
|
+
peerPeers
|
|
1036
|
+
);
|
|
813
1037
|
let semanticHits = [];
|
|
814
1038
|
if (typeof deps.embedQuery === "function") {
|
|
815
1039
|
try {
|
|
@@ -821,7 +1045,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
821
1045
|
queryEmbedding,
|
|
822
1046
|
limit,
|
|
823
1047
|
threshold,
|
|
824
|
-
logger
|
|
1048
|
+
logger,
|
|
1049
|
+
peerPeers
|
|
825
1050
|
);
|
|
826
1051
|
} catch (error) {
|
|
827
1052
|
logger.warn?.(
|
|
@@ -866,7 +1091,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
866
1091
|
});
|
|
867
1092
|
const keywords2 = deriveLexicalKeywords(input.query);
|
|
868
1093
|
const lexical2 = filterByDateRange(
|
|
869
|
-
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
1094
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger, peerPeers),
|
|
870
1095
|
input.dateFrom,
|
|
871
1096
|
input.dateTo
|
|
872
1097
|
);
|
|
@@ -892,7 +1117,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
892
1117
|
);
|
|
893
1118
|
const lists = await Promise.all(
|
|
894
1119
|
embeddings.map(
|
|
895
|
-
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
1120
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger, peerPeers)
|
|
896
1121
|
)
|
|
897
1122
|
);
|
|
898
1123
|
semantic = mergeByMaxScore(lists);
|
|
@@ -908,7 +1133,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
908
1133
|
}
|
|
909
1134
|
if (semantic.length === 0) {
|
|
910
1135
|
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
911
|
-
semantic = await runSemanticSearchForEmbedding(
|
|
1136
|
+
semantic = await runSemanticSearchForEmbedding(
|
|
1137
|
+
deps,
|
|
1138
|
+
input,
|
|
1139
|
+
queryEmbedding,
|
|
1140
|
+
limit,
|
|
1141
|
+
threshold,
|
|
1142
|
+
logger,
|
|
1143
|
+
peerPeers
|
|
1144
|
+
);
|
|
912
1145
|
}
|
|
913
1146
|
let lexical = [];
|
|
914
1147
|
const keywords = deriveLexicalKeywords(input.query);
|
|
@@ -917,13 +1150,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
917
1150
|
try {
|
|
918
1151
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
919
1152
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
1153
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
920
1154
|
lexical = (await Promise.all(
|
|
921
1155
|
lexFilters.map(
|
|
922
1156
|
(botId) => searchRawMessagesLexical({
|
|
923
1157
|
userId: input.userId,
|
|
924
1158
|
keywords,
|
|
925
1159
|
limit,
|
|
926
|
-
botId
|
|
1160
|
+
botId,
|
|
1161
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1162
|
+
...factTypes ? { factTypes } : {}
|
|
927
1163
|
})
|
|
928
1164
|
)
|
|
929
1165
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -951,20 +1187,272 @@ function createUnifiedSearch(deps = {}) {
|
|
|
951
1187
|
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
952
1188
|
};
|
|
953
1189
|
}
|
|
1190
|
+
async function searchUnifiedMemory(input) {
|
|
1191
|
+
const query = input.query.trim();
|
|
1192
|
+
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
1193
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1194
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1195
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1196
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1197
|
+
);
|
|
1198
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1199
|
+
const warnings = [];
|
|
1200
|
+
const peerScope = input.peerFilter && input.peerFilter.length > 0 ? await resolveScopePeer({
|
|
1201
|
+
userId: input.userId,
|
|
1202
|
+
peerFilter: input.peerFilter,
|
|
1203
|
+
scopeCheck: deps.peerScopeCheck
|
|
1204
|
+
}) : { peers: [], warnings: [] };
|
|
1205
|
+
warnings.push(...peerScope.warnings);
|
|
1206
|
+
const peerPeers = peerScope.peers;
|
|
1207
|
+
if (!query) {
|
|
1208
|
+
return {
|
|
1209
|
+
query,
|
|
1210
|
+
sources,
|
|
1211
|
+
results: [],
|
|
1212
|
+
count: 0,
|
|
1213
|
+
warnings
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
let memorySubs;
|
|
1217
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
1218
|
+
if (input.dateFrom || input.dateTo) {
|
|
1219
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
1220
|
+
}
|
|
1221
|
+
if (sources.includes("memory")) {
|
|
1222
|
+
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1223
|
+
if (isRawMessageStorageAvailable() || hasRawProviders) {
|
|
1224
|
+
try {
|
|
1225
|
+
memorySubs = await runMemorySource(
|
|
1226
|
+
input,
|
|
1227
|
+
limit,
|
|
1228
|
+
threshold,
|
|
1229
|
+
warnings,
|
|
1230
|
+
reasoningStrategy,
|
|
1231
|
+
reasoningInfo,
|
|
1232
|
+
peerPeers
|
|
1233
|
+
);
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
logger.warn?.("[memory-store] memory source failed:", error);
|
|
1236
|
+
warnings.push({
|
|
1237
|
+
source: "memory",
|
|
1238
|
+
code: "memory_search_failed",
|
|
1239
|
+
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
} else {
|
|
1243
|
+
warnings.push({
|
|
1244
|
+
source: "memory",
|
|
1245
|
+
code: "raw_message_storage_unavailable",
|
|
1246
|
+
message: "Raw memory storage is not available in this environment."
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const insightHits = [];
|
|
1251
|
+
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
1252
|
+
try {
|
|
1253
|
+
const hits = await deps.searchInsights({
|
|
1254
|
+
userId: input.userId,
|
|
1255
|
+
query: input.query,
|
|
1256
|
+
limit,
|
|
1257
|
+
threshold,
|
|
1258
|
+
botIds: input.botIds,
|
|
1259
|
+
includeArchived: input.includeArchivedInsights,
|
|
1260
|
+
authToken: input.authToken,
|
|
1261
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1262
|
+
});
|
|
1263
|
+
for (const hit of hits) {
|
|
1264
|
+
insightHits.push({
|
|
1265
|
+
type: "insight",
|
|
1266
|
+
id: hit.id,
|
|
1267
|
+
content: hit.content,
|
|
1268
|
+
similarity: hit.similarity,
|
|
1269
|
+
metadata: hit.metadata
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
logger.warn?.("[memory-store] insights source failed:", error);
|
|
1274
|
+
warnings.push({
|
|
1275
|
+
source: "insights",
|
|
1276
|
+
code: "insights_search_failed",
|
|
1277
|
+
message: error.message ?? "insights_search_failed"
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
} else if (sources.includes("insights")) ;
|
|
1281
|
+
const knowledgeHits = [];
|
|
1282
|
+
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
1283
|
+
try {
|
|
1284
|
+
const hits = await deps.searchKnowledge({
|
|
1285
|
+
userId: input.userId,
|
|
1286
|
+
query: input.query,
|
|
1287
|
+
options: {
|
|
1288
|
+
limit,
|
|
1289
|
+
threshold,
|
|
1290
|
+
documentIds: input.documentIds
|
|
1291
|
+
},
|
|
1292
|
+
authToken: input.authToken,
|
|
1293
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1294
|
+
});
|
|
1295
|
+
for (const hit of hits) {
|
|
1296
|
+
knowledgeHits.push(toKnowledgeResult(hit));
|
|
1297
|
+
}
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
1300
|
+
warnings.push({
|
|
1301
|
+
source: "knowledge",
|
|
1302
|
+
code: "knowledge_search_failed",
|
|
1303
|
+
message: error.message ?? "knowledge_search_failed"
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
} else if (sources.includes("knowledge")) ;
|
|
1307
|
+
const merged = mergeAcrossSources({
|
|
1308
|
+
memorySubs,
|
|
1309
|
+
insightHits,
|
|
1310
|
+
knowledgeHits,
|
|
1311
|
+
limit,
|
|
1312
|
+
strategy: mergeStrategy
|
|
1313
|
+
});
|
|
1314
|
+
const ranked = await applyReranker(deps.reranker, input.query, merged);
|
|
1315
|
+
const output = {
|
|
1316
|
+
query: input.query,
|
|
1317
|
+
sources,
|
|
1318
|
+
results: ranked,
|
|
1319
|
+
count: ranked.length,
|
|
1320
|
+
warnings
|
|
1321
|
+
};
|
|
1322
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
1323
|
+
output.reasoning = reasoningInfo;
|
|
1324
|
+
}
|
|
1325
|
+
return output;
|
|
1326
|
+
}
|
|
954
1327
|
async function searchRawMemorySemantically(input) {
|
|
955
1328
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
956
1329
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
957
1330
|
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1331
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1332
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1333
|
+
);
|
|
958
1334
|
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
959
1335
|
return mergeAcrossSources({
|
|
960
1336
|
memorySubs: sub,
|
|
961
1337
|
insightHits: [],
|
|
962
1338
|
knowledgeHits: [],
|
|
963
1339
|
limit,
|
|
964
|
-
strategy:
|
|
1340
|
+
strategy: mergeStrategy
|
|
965
1341
|
});
|
|
966
1342
|
}
|
|
967
|
-
|
|
1343
|
+
async function search(input) {
|
|
1344
|
+
const query = input.query.trim();
|
|
1345
|
+
const wantsSynthesis = typeof input.synthesize === "boolean" ? input.synthesize : typeof input.synthesize === "object" && input.synthesize !== null;
|
|
1346
|
+
const synthesisSchema = typeof input.synthesize === "object" && input.synthesize !== null ? input.synthesize.responseSchema ?? input.responseSchema : input.responseSchema;
|
|
1347
|
+
const peerScope = await resolveSearchScopePeers({
|
|
1348
|
+
deps,
|
|
1349
|
+
userId: input.userId,
|
|
1350
|
+
peerFilter: input.peerFilter
|
|
1351
|
+
});
|
|
1352
|
+
if (!query) {
|
|
1353
|
+
const empty = {
|
|
1354
|
+
query,
|
|
1355
|
+
sources: input.sources ? [...input.sources] : [],
|
|
1356
|
+
results: [],
|
|
1357
|
+
evidence: [],
|
|
1358
|
+
count: 0,
|
|
1359
|
+
warnings: peerScope.warnings,
|
|
1360
|
+
answer: ""
|
|
1361
|
+
};
|
|
1362
|
+
return empty;
|
|
1363
|
+
}
|
|
1364
|
+
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
|
|
1365
|
+
const wantsSummaryTier = tiers.includes("summary");
|
|
1366
|
+
const wantsRawTier = tiers.includes("raw");
|
|
1367
|
+
const wantsInsightTier = tiers.includes("insight");
|
|
1368
|
+
const wantsKnowledgeTier = tiers.includes("knowledge");
|
|
1369
|
+
const warnings = [...peerScope.warnings];
|
|
1370
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1371
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1372
|
+
let unifiedHits = [];
|
|
1373
|
+
let sources = input.sources ? [...input.sources] : [];
|
|
1374
|
+
let unifiedReasoning;
|
|
1375
|
+
if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier) {
|
|
1376
|
+
const inferredSources = sources.length > 0 ? sources : [
|
|
1377
|
+
...wantsRawTier ? ["memory"] : [],
|
|
1378
|
+
...wantsInsightTier ? ["insights"] : [],
|
|
1379
|
+
...wantsKnowledgeTier ? ["knowledge"] : []
|
|
1380
|
+
];
|
|
1381
|
+
const unified = await searchUnifiedMemory({
|
|
1382
|
+
...searchInputToUnified(input),
|
|
1383
|
+
sources: inferredSources.length > 0 ? inferredSources : void 0
|
|
1384
|
+
});
|
|
1385
|
+
unifiedHits = unified.results;
|
|
1386
|
+
sources = unified.sources;
|
|
1387
|
+
unifiedReasoning = unified.reasoning;
|
|
1388
|
+
warnings.push(...unified.warnings);
|
|
1389
|
+
}
|
|
1390
|
+
let summaryHits = [];
|
|
1391
|
+
if (wantsSummaryTier && typeof deps.searchSummaries === "function") {
|
|
1392
|
+
const summaryBucket = await gatherSummaries(
|
|
1393
|
+
deps,
|
|
1394
|
+
{ userId: input.userId, query: input.query, authToken: input.authToken },
|
|
1395
|
+
limit,
|
|
1396
|
+
threshold,
|
|
1397
|
+
logger,
|
|
1398
|
+
peerScope.peers
|
|
1399
|
+
);
|
|
1400
|
+
warnings.push(...summaryBucket.warnings);
|
|
1401
|
+
summaryHits = summaryBucket.hits;
|
|
1402
|
+
}
|
|
1403
|
+
const tierLists = [];
|
|
1404
|
+
if (unifiedHits.length > 0) {
|
|
1405
|
+
tierLists.push({ name: "memory", hits: unifiedHits });
|
|
1406
|
+
}
|
|
1407
|
+
if (summaryHits.length > 0) {
|
|
1408
|
+
tierLists.push({ name: "summary", hits: summaryHits });
|
|
1409
|
+
}
|
|
1410
|
+
let hits;
|
|
1411
|
+
if (tierLists.length === 0) {
|
|
1412
|
+
hits = [];
|
|
1413
|
+
} else if (tierLists.length === 1) {
|
|
1414
|
+
hits = tierLists[0].hits.slice(0, limit);
|
|
1415
|
+
} else {
|
|
1416
|
+
hits = mergeUnifiedMemorySearchResultsRrf(tierLists, limit);
|
|
1417
|
+
}
|
|
1418
|
+
const evidence = hits.map((hit) => ({
|
|
1419
|
+
id: hit.id,
|
|
1420
|
+
source: mapHitToEvidenceSource(hit),
|
|
1421
|
+
snippet: hit.content,
|
|
1422
|
+
score: hit.similarity,
|
|
1423
|
+
timestamp: getCandidateTimestamp(hit.metadata)
|
|
1424
|
+
}));
|
|
1425
|
+
const base = {
|
|
1426
|
+
query,
|
|
1427
|
+
sources,
|
|
1428
|
+
results: hits,
|
|
1429
|
+
evidence,
|
|
1430
|
+
count: hits.length,
|
|
1431
|
+
warnings
|
|
1432
|
+
};
|
|
1433
|
+
if (unifiedReasoning) {
|
|
1434
|
+
base.reasoning = unifiedReasoning;
|
|
1435
|
+
}
|
|
1436
|
+
if (!wantsSynthesis) {
|
|
1437
|
+
return base;
|
|
1438
|
+
}
|
|
1439
|
+
const { answer, warnings: synthWarnings } = await synthesizeAnswer({
|
|
1440
|
+
query,
|
|
1441
|
+
hits,
|
|
1442
|
+
responseSchema: synthesisSchema,
|
|
1443
|
+
deps,
|
|
1444
|
+
logger
|
|
1445
|
+
});
|
|
1446
|
+
base.answer = answer;
|
|
1447
|
+
base.warnings = [...base.warnings, ...synthWarnings];
|
|
1448
|
+
return base;
|
|
1449
|
+
}
|
|
1450
|
+
return {
|
|
1451
|
+
search,
|
|
1452
|
+
searchUnifiedMemory,
|
|
1453
|
+
searchRawMemorySemantically,
|
|
1454
|
+
consolidate: (input) => applyReflectedPlan(deps, {}, input)
|
|
1455
|
+
};
|
|
968
1456
|
}
|
|
969
1457
|
function mergeAcrossSources(input) {
|
|
970
1458
|
const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
|
|
@@ -996,9 +1484,283 @@ function mergeAcrossSources(input) {
|
|
|
996
1484
|
return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
|
|
997
1485
|
}
|
|
998
1486
|
|
|
1487
|
+
// src/search/apply-reflect.ts
|
|
1488
|
+
var DEFAULT_TIERS2 = ["summary", "raw", "insight", "knowledge"];
|
|
1489
|
+
function reflectTierToEvidenceTier(source) {
|
|
1490
|
+
switch (source) {
|
|
1491
|
+
case "raw":
|
|
1492
|
+
return "short";
|
|
1493
|
+
case "summary":
|
|
1494
|
+
return "long";
|
|
1495
|
+
default:
|
|
1496
|
+
return "mid";
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
function toMemoryEvidenceRecord(input, fallbackUserId) {
|
|
1500
|
+
const tier = reflectTierToEvidenceTier(input.source);
|
|
1501
|
+
const timestamp = input.timestamp ?? Date.now();
|
|
1502
|
+
return {
|
|
1503
|
+
id: input.id,
|
|
1504
|
+
userId: fallbackUserId,
|
|
1505
|
+
timestamp,
|
|
1506
|
+
tier,
|
|
1507
|
+
text: input.snippet,
|
|
1508
|
+
metadata: {
|
|
1509
|
+
source: input.source,
|
|
1510
|
+
score: input.score,
|
|
1511
|
+
...input.timestamp !== void 0 ? { originalTimestamp: input.timestamp } : {}
|
|
1512
|
+
}
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
async function applyReflectedPlan(deps, store, input, _logger) {
|
|
1516
|
+
const query = input.query.trim();
|
|
1517
|
+
const warnings = [];
|
|
1518
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1519
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1520
|
+
const tiers = input.tiers && input.tiers.length > 0 ? input.tiers : DEFAULT_TIERS2;
|
|
1521
|
+
const searchInput = {
|
|
1522
|
+
userId: input.userId,
|
|
1523
|
+
query,
|
|
1524
|
+
botIds: input.botIds,
|
|
1525
|
+
dateFrom: input.dateFrom,
|
|
1526
|
+
dateTo: input.dateTo,
|
|
1527
|
+
tiers,
|
|
1528
|
+
limit,
|
|
1529
|
+
threshold,
|
|
1530
|
+
authToken: input.authToken,
|
|
1531
|
+
peerFilter: input.peerFilter,
|
|
1532
|
+
synthesize: true
|
|
1533
|
+
};
|
|
1534
|
+
const search = createUnifiedSearch(deps);
|
|
1535
|
+
const result = await search.search(searchInput);
|
|
1536
|
+
warnings.push(...result.warnings);
|
|
1537
|
+
const evidence = result.evidence.map((item) => ({
|
|
1538
|
+
id: item.id,
|
|
1539
|
+
source: item.source ?? "raw",
|
|
1540
|
+
snippet: item.snippet,
|
|
1541
|
+
score: item.score,
|
|
1542
|
+
timestamp: item.timestamp
|
|
1543
|
+
}));
|
|
1544
|
+
const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
|
|
1545
|
+
const applyInput = {
|
|
1546
|
+
userId: input.userId,
|
|
1547
|
+
ownerScope: input.ownerScope,
|
|
1548
|
+
records,
|
|
1549
|
+
plan: input.plan,
|
|
1550
|
+
dryRun: input.dryRun,
|
|
1551
|
+
expectedVersion: input.expectedVersion,
|
|
1552
|
+
getClusterKey: input.getClusterKey ?? defaultClusterKey,
|
|
1553
|
+
llmReview: deps.reasoning?.complete ? {
|
|
1554
|
+
complete: deps.reasoning.complete,
|
|
1555
|
+
maxTokens: input.llmPlanReview?.maxTokens
|
|
1556
|
+
} : void 0,
|
|
1557
|
+
graphStore: input.graphStore ?? store.graphStore,
|
|
1558
|
+
storage: input.storage ?? store.storage
|
|
1559
|
+
};
|
|
1560
|
+
const applyOutput = await applyReflectedConsolidationPlan(applyInput);
|
|
1561
|
+
for (const warning of applyOutput.warnings) {
|
|
1562
|
+
warnings.push({
|
|
1563
|
+
source: "memory",
|
|
1564
|
+
code: warning.code,
|
|
1565
|
+
message: warning.message
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
const summaryAnswer = makePlanSummary(applyOutput.plan);
|
|
1569
|
+
return {
|
|
1570
|
+
answer: result.answer ? `${result.answer}
|
|
1571
|
+
|
|
1572
|
+
Plan summary:
|
|
1573
|
+
${summaryAnswer}` : summaryAnswer,
|
|
1574
|
+
plan: applyOutput.plan,
|
|
1575
|
+
persistenceResult: applyOutput.persistenceResult,
|
|
1576
|
+
deprecationCounts: applyOutput.deprecationCounts,
|
|
1577
|
+
warnings,
|
|
1578
|
+
applied: applyOutput.applied,
|
|
1579
|
+
evidence
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
function defaultClusterKey(record) {
|
|
1583
|
+
return record.text ?? record.id;
|
|
1584
|
+
}
|
|
1585
|
+
function makePlanSummary(plan) {
|
|
1586
|
+
const lines = [];
|
|
1587
|
+
const counts = {
|
|
1588
|
+
preserve: plan.actions.preserve.length,
|
|
1589
|
+
observe: plan.actions.observe.length,
|
|
1590
|
+
decay: plan.actions.decay.length,
|
|
1591
|
+
deprecate: plan.actions.deprecate.length
|
|
1592
|
+
};
|
|
1593
|
+
lines.push(
|
|
1594
|
+
`- preserve=${counts.preserve} observe=${counts.observe} decay=${counts.decay} deprecate=${counts.deprecate}`
|
|
1595
|
+
);
|
|
1596
|
+
for (const entry of plan.actions.deprecate.slice(0, 5)) {
|
|
1597
|
+
lines.push(
|
|
1598
|
+
` deprecate ${entry.recordIds.length} record(s) from cluster ${entry.clusterKey} -> ${entry.supersededBySummaryId ?? entry.winningClusterKey}`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
return lines.join("\n");
|
|
1602
|
+
}
|
|
1603
|
+
var DEFAULT_MAX_FACTS = 1e3;
|
|
1604
|
+
var LOW_CONFIDENCE_THRESHOLD = 0.1;
|
|
1605
|
+
function toHRR(data) {
|
|
1606
|
+
const arr = data instanceof Float32Array ? data : new Float32Array(data);
|
|
1607
|
+
return { dim: arr.length, data: arr };
|
|
1608
|
+
}
|
|
1609
|
+
function generateFactId() {
|
|
1610
|
+
const stamp = Date.now().toString(36);
|
|
1611
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
1612
|
+
return `vsa-${stamp}-${rand}`;
|
|
1613
|
+
}
|
|
1614
|
+
function createVsaRecall(storage) {
|
|
1615
|
+
return {
|
|
1616
|
+
async storeFact(input) {
|
|
1617
|
+
const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
|
|
1618
|
+
const fillerVector = vsaNormalizeVector(input.fillerVector, "fillerVector");
|
|
1619
|
+
vsaAssertSameDim(roleVector, fillerVector, "storeFact");
|
|
1620
|
+
const dim = input.dim ?? roleVector.length;
|
|
1621
|
+
if (roleVector.length !== dim) {
|
|
1622
|
+
throw new Error(`storeFact: roleVector.length (${roleVector.length}) must equal dim (${dim})`);
|
|
1623
|
+
}
|
|
1624
|
+
const now = Date.now();
|
|
1625
|
+
const fact = {
|
|
1626
|
+
factId: input.factId ?? generateFactId(),
|
|
1627
|
+
userId: input.userId,
|
|
1628
|
+
roleLabel: input.roleLabel,
|
|
1629
|
+
fillerLabel: input.fillerLabel,
|
|
1630
|
+
roleVector,
|
|
1631
|
+
fillerVector,
|
|
1632
|
+
dim,
|
|
1633
|
+
scopeTag: input.scopeTag ?? "default",
|
|
1634
|
+
botId: input.botId,
|
|
1635
|
+
createdAt: now
|
|
1636
|
+
};
|
|
1637
|
+
await storage.storeFact(fact);
|
|
1638
|
+
return { factId: fact.factId, createdAt: now };
|
|
1639
|
+
},
|
|
1640
|
+
async recall(input) {
|
|
1641
|
+
const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
|
|
1642
|
+
const scopeTag = input.scopeTag ?? "default";
|
|
1643
|
+
const maxFacts = input.maxFacts ?? DEFAULT_MAX_FACTS;
|
|
1644
|
+
if (input.vocabulary.length === 0) {
|
|
1645
|
+
throw new Error("vsaRecall: vocabulary must contain at least one entry");
|
|
1646
|
+
}
|
|
1647
|
+
const facts = await storage.queryFacts({
|
|
1648
|
+
userId: input.userId,
|
|
1649
|
+
scopeTag,
|
|
1650
|
+
botId: input.botId,
|
|
1651
|
+
includeDeprecated: false,
|
|
1652
|
+
limit: maxFacts
|
|
1653
|
+
});
|
|
1654
|
+
const warnings = [];
|
|
1655
|
+
if (facts.length === 0) {
|
|
1656
|
+
warnings.push({
|
|
1657
|
+
code: "vsa_no_facts",
|
|
1658
|
+
message: `No VSA facts for userId=${input.userId} scopeTag=${scopeTag}`
|
|
1659
|
+
});
|
|
1660
|
+
return {
|
|
1661
|
+
fillerLabel: "",
|
|
1662
|
+
score: 0,
|
|
1663
|
+
allScores: [],
|
|
1664
|
+
factCount: 0,
|
|
1665
|
+
warnings
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
const refDim = facts[0].dim;
|
|
1669
|
+
const dimMatchedFacts = facts.filter((fact) => {
|
|
1670
|
+
if (fact.dim === refDim) return true;
|
|
1671
|
+
warnings.push({
|
|
1672
|
+
code: "vsa_dim_mismatch",
|
|
1673
|
+
message: `Fact ${fact.factId} has dim=${fact.dim}, expected ${refDim}; skipping`
|
|
1674
|
+
});
|
|
1675
|
+
return false;
|
|
1676
|
+
});
|
|
1677
|
+
if (roleVector.length !== refDim) {
|
|
1678
|
+
throw new Error(
|
|
1679
|
+
`vsaRecall: roleVector dim (${roleVector.length}) must match stored fact dim (${refDim})`
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
if (dimMatchedFacts.length === 0) {
|
|
1683
|
+
warnings.push({
|
|
1684
|
+
code: "vsa_no_facts",
|
|
1685
|
+
message: `All stored facts for userId=${input.userId} scopeTag=${scopeTag} had a non-matching dim`
|
|
1686
|
+
});
|
|
1687
|
+
return {
|
|
1688
|
+
fillerLabel: "",
|
|
1689
|
+
score: 0,
|
|
1690
|
+
allScores: [],
|
|
1691
|
+
factCount: 0,
|
|
1692
|
+
warnings
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
const memoryVec = superpose(
|
|
1696
|
+
dimMatchedFacts.map((fact) => bind(toHRR(fact.roleVector), toHRR(fact.fillerVector)))
|
|
1697
|
+
);
|
|
1698
|
+
const candidate = unbind(memoryVec, toHRR(roleVector));
|
|
1699
|
+
const vocabVecs = input.vocabulary.map(
|
|
1700
|
+
(entry) => toHRR(vsaNormalizeVector(entry.vector, `vocabulary[${entry.label}].vector`))
|
|
1701
|
+
);
|
|
1702
|
+
const bestVec = cleanup(candidate, vocabVecs);
|
|
1703
|
+
const bestIdx = vocabVecs.indexOf(bestVec);
|
|
1704
|
+
const allScores = input.vocabulary.map((entry, idx) => ({
|
|
1705
|
+
label: entry.label,
|
|
1706
|
+
score: cosineSimilarity(candidate, vocabVecs[idx])
|
|
1707
|
+
}));
|
|
1708
|
+
allScores.sort((a, b) => b.score - a.score);
|
|
1709
|
+
const fillerLabel = bestIdx >= 0 ? input.vocabulary[bestIdx].label : "";
|
|
1710
|
+
const score = allScores[0]?.score ?? 0;
|
|
1711
|
+
if (score < LOW_CONFIDENCE_THRESHOLD) {
|
|
1712
|
+
warnings.push({
|
|
1713
|
+
code: "vsa_low_confidence",
|
|
1714
|
+
message: `Best match "${fillerLabel}" score ${score.toFixed(3)} below ${LOW_CONFIDENCE_THRESHOLD}`
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
if (facts.length >= maxFacts) {
|
|
1718
|
+
warnings.push({
|
|
1719
|
+
code: "vsa_fact_limit_reached",
|
|
1720
|
+
message: `Loaded ${facts.length} facts (cap=${maxFacts}); recall may degrade with more`
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
return {
|
|
1724
|
+
fillerLabel,
|
|
1725
|
+
score,
|
|
1726
|
+
allScores,
|
|
1727
|
+
factCount: facts.length,
|
|
1728
|
+
warnings
|
|
1729
|
+
};
|
|
1730
|
+
},
|
|
1731
|
+
async listFacts(input) {
|
|
1732
|
+
const facts = await storage.queryFacts({
|
|
1733
|
+
userId: input.userId,
|
|
1734
|
+
scopeTag: input.scopeTag,
|
|
1735
|
+
botId: input.botId,
|
|
1736
|
+
includeDeprecated: input.includeDeprecated
|
|
1737
|
+
});
|
|
1738
|
+
return facts.map((fact) => ({
|
|
1739
|
+
factId: fact.factId,
|
|
1740
|
+
userId: fact.userId,
|
|
1741
|
+
roleLabel: fact.roleLabel,
|
|
1742
|
+
fillerLabel: fact.fillerLabel,
|
|
1743
|
+
scopeTag: fact.scopeTag,
|
|
1744
|
+
botId: fact.botId,
|
|
1745
|
+
createdAt: fact.createdAt,
|
|
1746
|
+
deprecatedAt: fact.deprecatedAt
|
|
1747
|
+
}));
|
|
1748
|
+
},
|
|
1749
|
+
async forget(input) {
|
|
1750
|
+
const result = await storage.deprecateFacts({
|
|
1751
|
+
userId: input.userId,
|
|
1752
|
+
factIds: input.factIds,
|
|
1753
|
+
reason: input.reason
|
|
1754
|
+
});
|
|
1755
|
+
return { deprecatedCount: result.deprecatedCount };
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
|
|
999
1760
|
// src/http.ts
|
|
1000
1761
|
init_chroma_memory_index();
|
|
1001
1762
|
init_raw_message_store();
|
|
1763
|
+
init_sqlite_raw_message_store();
|
|
1002
1764
|
async function embedMissingMessages(messages, deps) {
|
|
1003
1765
|
if (typeof deps.embedQuery !== "function") {
|
|
1004
1766
|
return messages;
|
|
@@ -1050,6 +1812,9 @@ async function startHttpServer(options = {}) {
|
|
|
1050
1812
|
return [];
|
|
1051
1813
|
}
|
|
1052
1814
|
});
|
|
1815
|
+
const vsaDbPath = resolveSQLiteRawMessageDbPath(options.dbPath);
|
|
1816
|
+
const vsaStorage = await getSQLiteVsaStore({ dbPath: vsaDbPath });
|
|
1817
|
+
const vsa = createVsaRecall(vsaStorage);
|
|
1053
1818
|
const app = new Hono();
|
|
1054
1819
|
app.get("/health", (c) => c.json({ ok: true, store: "memory", ts: Date.now() }));
|
|
1055
1820
|
app.post("/v1/search", async (c) => {
|
|
@@ -1059,17 +1824,62 @@ async function startHttpServer(options = {}) {
|
|
|
1059
1824
|
if (!userId || !query) {
|
|
1060
1825
|
return c.json({ error: "userId and query are required" }, 400);
|
|
1061
1826
|
}
|
|
1062
|
-
const
|
|
1827
|
+
const wantsSynthesis = typeof body.synthesize === "boolean" ? body.synthesize : typeof body.synthesize === "object" && body.synthesize !== null;
|
|
1828
|
+
const responseSchema = typeof body.synthesize === "object" && body.synthesize !== null ? body.synthesize.responseSchema ?? body.responseSchema : body.responseSchema;
|
|
1829
|
+
const input = {
|
|
1063
1830
|
userId,
|
|
1064
1831
|
query,
|
|
1065
|
-
sources: body.sources,
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1832
|
+
sources: Array.isArray(body.sources) ? body.sources : void 0,
|
|
1833
|
+
tiers: Array.isArray(body.tiers) ? body.tiers : void 0,
|
|
1834
|
+
limit: typeof body.limit === "number" ? body.limit : void 0,
|
|
1835
|
+
threshold: typeof body.threshold === "number" ? body.threshold : void 0,
|
|
1836
|
+
botIds: Array.isArray(body.botIds) ? body.botIds : void 0,
|
|
1837
|
+
documentIds: Array.isArray(body.documentIds) ? body.documentIds : void 0,
|
|
1838
|
+
includeArchivedInsights: body.includeArchivedInsights === true,
|
|
1839
|
+
authToken: typeof body.authToken === "string" ? body.authToken : void 0,
|
|
1840
|
+
dateFrom: typeof body.dateFrom === "string" ? body.dateFrom : void 0,
|
|
1841
|
+
dateTo: typeof body.dateTo === "string" ? body.dateTo : void 0,
|
|
1842
|
+
...wantsSynthesis ? {
|
|
1843
|
+
synthesize: {
|
|
1844
|
+
...responseSchema ? { responseSchema } : {}
|
|
1845
|
+
}
|
|
1846
|
+
} : {}
|
|
1847
|
+
};
|
|
1848
|
+
const result = await search.search(input);
|
|
1849
|
+
return c.json(result);
|
|
1850
|
+
});
|
|
1851
|
+
app.post("/v1/consolidate:apply", async (c) => {
|
|
1852
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1853
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
1854
|
+
const query = typeof body.query === "string" ? body.query : "";
|
|
1855
|
+
const ownerScope = body.ownerScope ?? {};
|
|
1856
|
+
if (!userId || !query) {
|
|
1857
|
+
return c.json({ error: "userId and query are required" }, 400);
|
|
1858
|
+
}
|
|
1859
|
+
if (typeof ownerScope.userId !== "string") {
|
|
1860
|
+
return c.json({ error: "ownerScope.userId is required" }, 400);
|
|
1861
|
+
}
|
|
1862
|
+
const input = {
|
|
1863
|
+
userId,
|
|
1864
|
+
query,
|
|
1865
|
+
ownerScope: { userId: ownerScope.userId },
|
|
1866
|
+
botIds: Array.isArray(body.botIds) ? body.botIds : void 0,
|
|
1867
|
+
dateFrom: typeof body.dateFrom === "string" ? body.dateFrom : void 0,
|
|
1868
|
+
dateTo: typeof body.dateTo === "string" ? body.dateTo : void 0,
|
|
1869
|
+
tiers: Array.isArray(body.tiers) ? body.tiers : void 0,
|
|
1870
|
+
limit: typeof body.limit === "number" ? body.limit : void 0,
|
|
1871
|
+
threshold: typeof body.threshold === "number" ? body.threshold : void 0,
|
|
1872
|
+
dryRun: body.dryRun === true,
|
|
1873
|
+
expectedVersion: typeof body.expectedVersion === "string" ? body.expectedVersion : void 0,
|
|
1874
|
+
authToken: typeof body.authToken === "string" ? body.authToken : void 0,
|
|
1875
|
+
llmPlanReview: body.llmPlanReview && typeof body.llmPlanReview === "object" ? {
|
|
1876
|
+
maxTokens: typeof body.llmPlanReview.maxTokens === "number" ? body.llmPlanReview.maxTokens : void 0
|
|
1877
|
+
} : void 0
|
|
1878
|
+
};
|
|
1879
|
+
const result = await applyReflectedPlan(
|
|
1880
|
+
{ ...options.unified ?? {} },
|
|
1881
|
+
{ graphStore: options.graphStore, storage: options.storage },
|
|
1882
|
+
input);
|
|
1073
1883
|
return c.json(result);
|
|
1074
1884
|
});
|
|
1075
1885
|
app.post("/v1/raw-messages", async (c) => {
|
|
@@ -1135,12 +1945,98 @@ async function startHttpServer(options = {}) {
|
|
|
1135
1945
|
if (!row) return c.json({ error: "not found" }, 404);
|
|
1136
1946
|
return c.json({ message: row });
|
|
1137
1947
|
});
|
|
1948
|
+
app.post("/v1/vsa/store", async (c) => {
|
|
1949
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1950
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
1951
|
+
const roleLabel = typeof body.roleLabel === "string" ? body.roleLabel : null;
|
|
1952
|
+
const fillerLabel = typeof body.fillerLabel === "string" ? body.fillerLabel : null;
|
|
1953
|
+
if (!userId || !roleLabel || !fillerLabel) {
|
|
1954
|
+
return c.json({ error: "userId, roleLabel, and fillerLabel are required" }, 400);
|
|
1955
|
+
}
|
|
1956
|
+
if (!Array.isArray(body.roleVector) || !Array.isArray(body.fillerVector)) {
|
|
1957
|
+
return c.json({ error: "roleVector[] and fillerVector[] are required" }, 400);
|
|
1958
|
+
}
|
|
1959
|
+
try {
|
|
1960
|
+
const result = await vsa.storeFact({
|
|
1961
|
+
userId,
|
|
1962
|
+
roleLabel,
|
|
1963
|
+
fillerLabel,
|
|
1964
|
+
roleVector: body.roleVector,
|
|
1965
|
+
fillerVector: body.fillerVector,
|
|
1966
|
+
scopeTag: typeof body.scopeTag === "string" ? body.scopeTag : void 0,
|
|
1967
|
+
botId: typeof body.botId === "string" ? body.botId : void 0,
|
|
1968
|
+
factId: typeof body.factId === "string" ? body.factId : void 0
|
|
1969
|
+
});
|
|
1970
|
+
return c.json(result);
|
|
1971
|
+
} catch (error) {
|
|
1972
|
+
return c.json({ error: error.message ?? "vsa.store failed" }, 400);
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
app.post("/v1/vsa/recall", async (c) => {
|
|
1976
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1977
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
1978
|
+
const roleLabel = typeof body.roleLabel === "string" ? body.roleLabel : null;
|
|
1979
|
+
const roleVector = Array.isArray(body.roleVector) ? body.roleVector : null;
|
|
1980
|
+
const vocabulary = Array.isArray(body.vocabulary) ? body.vocabulary : null;
|
|
1981
|
+
if (!userId || !roleLabel || !roleVector || !vocabulary) {
|
|
1982
|
+
return c.json({ error: "userId, roleLabel, roleVector[], and vocabulary[] are required" }, 400);
|
|
1983
|
+
}
|
|
1984
|
+
try {
|
|
1985
|
+
const result = await vsa.recall({
|
|
1986
|
+
userId,
|
|
1987
|
+
roleLabel,
|
|
1988
|
+
roleVector,
|
|
1989
|
+
vocabulary,
|
|
1990
|
+
scopeTag: typeof body.scopeTag === "string" ? body.scopeTag : void 0,
|
|
1991
|
+
botId: typeof body.botId === "string" ? body.botId : void 0,
|
|
1992
|
+
maxFacts: typeof body.maxFacts === "number" ? body.maxFacts : void 0
|
|
1993
|
+
});
|
|
1994
|
+
return c.json(result);
|
|
1995
|
+
} catch (error) {
|
|
1996
|
+
return c.json({ error: error.message ?? "vsa.recall failed" }, 400);
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
app.post("/v1/vsa/list", async (c) => {
|
|
2000
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2001
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
2002
|
+
if (!userId) return c.json({ error: "userId is required" }, 400);
|
|
2003
|
+
try {
|
|
2004
|
+
const facts = await vsa.listFacts({
|
|
2005
|
+
userId,
|
|
2006
|
+
scopeTag: typeof body.scopeTag === "string" ? body.scopeTag : void 0,
|
|
2007
|
+
botId: typeof body.botId === "string" ? body.botId : void 0,
|
|
2008
|
+
includeDeprecated: body.includeDeprecated === true
|
|
2009
|
+
});
|
|
2010
|
+
return c.json({ facts });
|
|
2011
|
+
} catch (error) {
|
|
2012
|
+
return c.json({ error: error.message ?? "vsa.list failed" }, 400);
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2015
|
+
app.post("/v1/vsa/forget", async (c) => {
|
|
2016
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2017
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
2018
|
+
const factIds = Array.isArray(body.factIds) ? body.factIds : null;
|
|
2019
|
+
if (!userId || !factIds) {
|
|
2020
|
+
return c.json({ error: "userId and factIds[] are required" }, 400);
|
|
2021
|
+
}
|
|
2022
|
+
try {
|
|
2023
|
+
const result = await vsa.forget({
|
|
2024
|
+
userId,
|
|
2025
|
+
factIds,
|
|
2026
|
+
reason: typeof body.reason === "string" ? body.reason : void 0
|
|
2027
|
+
});
|
|
2028
|
+
return c.json(result);
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
return c.json({ error: error.message ?? "vsa.forget failed" }, 400);
|
|
2031
|
+
}
|
|
2032
|
+
});
|
|
1138
2033
|
const server = serve({ fetch: app.fetch, port, hostname: host });
|
|
1139
2034
|
return {
|
|
1140
2035
|
url: `http://${host}:${port}`,
|
|
1141
2036
|
port,
|
|
1142
2037
|
async stop() {
|
|
1143
2038
|
await rawStore.close();
|
|
2039
|
+
await closeSQLiteVsaStore();
|
|
1144
2040
|
server.close();
|
|
1145
2041
|
}
|
|
1146
2042
|
};
|