@getplumb/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/dist/bm25.d.ts +23 -0
  4. package/dist/bm25.d.ts.map +1 -0
  5. package/dist/bm25.js +74 -0
  6. package/dist/bm25.js.map +1 -0
  7. package/dist/chunker.d.ts +35 -0
  8. package/dist/chunker.d.ts.map +1 -0
  9. package/dist/chunker.js +45 -0
  10. package/dist/chunker.js.map +1 -0
  11. package/dist/context-builder.d.ts +33 -0
  12. package/dist/context-builder.d.ts.map +1 -0
  13. package/dist/context-builder.js +101 -0
  14. package/dist/context-builder.js.map +1 -0
  15. package/dist/embedder.d.ts +34 -0
  16. package/dist/embedder.d.ts.map +1 -0
  17. package/dist/embedder.js +88 -0
  18. package/dist/embedder.js.map +1 -0
  19. package/dist/extractor.d.ts +21 -0
  20. package/dist/extractor.d.ts.map +1 -0
  21. package/dist/extractor.js +89 -0
  22. package/dist/extractor.js.map +1 -0
  23. package/dist/fact-search.d.ts +28 -0
  24. package/dist/fact-search.d.ts.map +1 -0
  25. package/dist/fact-search.js +155 -0
  26. package/dist/fact-search.js.map +1 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +12 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/llm-client.d.ts +28 -0
  32. package/dist/llm-client.d.ts.map +1 -0
  33. package/dist/llm-client.js +115 -0
  34. package/dist/llm-client.js.map +1 -0
  35. package/dist/local-store.d.ts +99 -0
  36. package/dist/local-store.d.ts.map +1 -0
  37. package/dist/local-store.js +292 -0
  38. package/dist/local-store.js.map +1 -0
  39. package/dist/raw-log-search.d.ts +33 -0
  40. package/dist/raw-log-search.d.ts.map +1 -0
  41. package/dist/raw-log-search.js +137 -0
  42. package/dist/raw-log-search.js.map +1 -0
  43. package/dist/read-path.d.ts +60 -0
  44. package/dist/read-path.d.ts.map +1 -0
  45. package/dist/read-path.js +76 -0
  46. package/dist/read-path.js.map +1 -0
  47. package/dist/schema.d.ts +34 -0
  48. package/dist/schema.d.ts.map +1 -0
  49. package/dist/schema.js +119 -0
  50. package/dist/schema.js.map +1 -0
  51. package/dist/scorer.d.ts +30 -0
  52. package/dist/scorer.d.ts.map +1 -0
  53. package/dist/scorer.js +50 -0
  54. package/dist/scorer.js.map +1 -0
  55. package/dist/store.d.ts +25 -0
  56. package/dist/store.d.ts.map +1 -0
  57. package/dist/store.js +2 -0
  58. package/dist/store.js.map +1 -0
  59. package/dist/types.d.ts +44 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +7 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +43 -0
@@ -0,0 +1,89 @@
1
+ import { callLLM } from './llm-client.js';
2
+ import { DecayRate } from './types.js';
3
+ /** Build the extraction prompt from a conversation exchange. */
4
+ function buildExtractionPrompt(exchange) {
5
+ return (`Extract facts from this conversation exchange worth remembering in future conversations.\n\n` +
6
+ `User: ${exchange.userMessage}\n` +
7
+ `Agent: ${exchange.agentResponse}\n\n` +
8
+ `Rules:\n` +
9
+ `- Output ONLY a valid JSON array. No prose, no explanation, no markdown fences.\n` +
10
+ `- Each item: {"subject": string, "predicate": string, "object": string, "context": string, "confidence": number 0-1, "decay_rate": "slow"|"medium"|"fast"}\n` +
11
+ `- decay_rate: slow=identity/stable prefs/decisions, medium=project context/tool choices, fast=transient state\n` +
12
+ `- confidence: 0.95 for explicit statements, 0.7-0.85 for inferred\n` +
13
+ `- Output [] if nothing is worth remembering\n` +
14
+ `- Skip: pleasantries, small talk, transient questions, tool outputs, error messages\n` +
15
+ `- Extract: decisions, preferences, facts about people/projects/systems, deadlines, named entities\n\n` +
16
+ `JSON array:`);
17
+ }
18
+ /**
19
+ * Parse a JSON array from LLM output, tolerating markdown code fences and leading prose.
20
+ * Returns empty array on any parse failure.
21
+ */
22
+ function parseJsonArray(text) {
23
+ // Find the first '[' and last ']' — extract just that substring
24
+ const start = text.indexOf('[');
25
+ const end = text.lastIndexOf(']');
26
+ if (start === -1 || end === -1 || end < start)
27
+ return [];
28
+ const jsonSlice = text.slice(start, end + 1).trim();
29
+ const parsed = JSON.parse(jsonSlice);
30
+ if (!Array.isArray(parsed))
31
+ return [];
32
+ return parsed;
33
+ }
34
+ function toDecayRate(raw) {
35
+ if (raw === 'slow')
36
+ return DecayRate.slow;
37
+ if (raw === 'fast')
38
+ return DecayRate.fast;
39
+ return DecayRate.medium;
40
+ }
41
+ /**
42
+ * Extract facts from a conversation exchange via an LLM call.
43
+ *
44
+ * Makes one LLM call, parses the JSON array response, persists each fact via
45
+ * the provided store, and returns the stored Fact[].
46
+ *
47
+ * Dedup strategy: always insert as a new entry — never update an existing fact
48
+ * with the same subject+predicate. Decay scoring (T-006) handles ranking.
49
+ *
50
+ * @param exchange - The conversation exchange to extract facts from.
51
+ * @param userId - The user ID to scope facts to (passed to store.store()).
52
+ * NOTE: LocalStore captures userId at construction time, so
53
+ * this param is accepted here for documentation/future use
54
+ * but the store itself enforces the scope.
55
+ * @param store - The MemoryStore instance to persist facts into.
56
+ * @param llmFn - Optional LLM function to use (injectable for testing).
57
+ */
58
+ export async function extractFacts(exchange, _userId, store, llmFn = callLLM) {
59
+ const prompt = buildExtractionPrompt(exchange);
60
+ const response = await llmFn(prompt);
61
+ let rawFacts;
62
+ try {
63
+ rawFacts = parseJsonArray(response);
64
+ }
65
+ catch {
66
+ console.error('[plumb/extractor] Failed to parse LLM response as JSON array:', response);
67
+ return [];
68
+ }
69
+ const facts = [];
70
+ for (const raw of rawFacts) {
71
+ const factInput = {
72
+ subject: String(raw.subject),
73
+ predicate: String(raw.predicate),
74
+ object: String(raw.object),
75
+ confidence: Math.max(0, Math.min(1, Number(raw.confidence))),
76
+ decayRate: toDecayRate(String(raw.decay_rate)),
77
+ timestamp: new Date(),
78
+ sourceSessionId: exchange.sessionId,
79
+ ...(exchange.sessionLabel !== undefined ? { sourceSessionLabel: exchange.sessionLabel } : {}),
80
+ ...(raw.context !== undefined ? { context: String(raw.context) } : {}),
81
+ };
82
+ // Dedup: always insert as a new entry (do not update existing same subject+predicate).
83
+ // The store always inserts; decay scoring handles which entry wins at retrieval time.
84
+ const id = await store.store(factInput);
85
+ facts.push({ id, ...factInput });
86
+ }
87
+ return facts;
88
+ }
89
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","sourceRoot":"","sources":["../src/extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Fact hybrid search (Layer 2 retrieval).
3
+ *
4
+ * Pipeline:
5
+ * 1. BM25 keyword search over concatenated fact text (subject+predicate+object+context)
6
+ * 2. KNN vector search via sqlite-vec vec_facts virtual table
7
+ * 3. Reciprocal Rank Fusion (RRF, k=60) merges both ranked lists
8
+ * 4. Recency decay: score *= e^(-lambda × age_in_days), using per-fact decay_rate
9
+ * 5. Cross-encoder reranker (top-20 candidates → Xenova/ms-marco-MiniLM-L-6-v2)
10
+ * 6. Return top-k by reranker score (falls back to RRF×decay if reranker fails)
11
+ *
12
+ * Decay lambdas by fact decay_rate:
13
+ * slow=0.003 (half-life ~231 days) — identity, stable preferences
14
+ * medium=0.012 (half-life ~58 days) — project context, tool choices
15
+ * fast=0.05 (half-life ~14 days) — transient state
16
+ */
17
+ import type Database from 'better-sqlite3';
18
+ import type { SearchResult } from './types.js';
19
+ /**
20
+ * Hybrid search over facts.
21
+ *
22
+ * @param db The better-sqlite3 Database instance (with sqlite-vec loaded)
23
+ * @param userId Scopes the search to this user's data
24
+ * @param query Natural language query string
25
+ * @param limit Number of results to return (default 20)
26
+ */
27
+ export declare function searchFacts(db: Database.Database, userId: string, query: string, limit?: number): Promise<readonly SearchResult[]>;
28
+ //# sourceMappingURL=fact-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-search.d.ts","sourceRoot":"","sources":["../src/fact-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA6F/C;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,KAAK,SAAK,GACT,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAgGlC"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Fact hybrid search (Layer 2 retrieval).
3
+ *
4
+ * Pipeline:
5
+ * 1. BM25 keyword search over concatenated fact text (subject+predicate+object+context)
6
+ * 2. KNN vector search via sqlite-vec vec_facts virtual table
7
+ * 3. Reciprocal Rank Fusion (RRF, k=60) merges both ranked lists
8
+ * 4. Recency decay: score *= e^(-lambda × age_in_days), using per-fact decay_rate
9
+ * 5. Cross-encoder reranker (top-20 candidates → Xenova/ms-marco-MiniLM-L-6-v2)
10
+ * 6. Return top-k by reranker score (falls back to RRF×decay if reranker fails)
11
+ *
12
+ * Decay lambdas by fact decay_rate:
13
+ * slow=0.003 (half-life ~231 days) — identity, stable preferences
14
+ * medium=0.012 (half-life ~58 days) — project context, tool choices
15
+ * fast=0.05 (half-life ~14 days) — transient state
16
+ */
17
+ import { Bm25 } from './bm25.js';
18
+ import { embedQuery, rerankScores } from './embedder.js';
19
+ // RRF constant (standard k=60).
20
+ const RRF_K = 60;
21
+ // Number of candidates passed to the cross-encoder.
22
+ const RERANK_TOP_K = 20;
23
+ // Decay lambdas by fact decay_rate field.
24
+ const DECAY_LAMBDAS = {
25
+ slow: 0.003,
26
+ medium: 0.012,
27
+ fast: 0.05,
28
+ };
29
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
30
+ function factText(row) {
31
+ return `${row.subject} ${row.predicate} ${row.object} ${row.context ?? ''}`.trim();
32
+ }
33
+ function ageInDays(timestamp) {
34
+ return (Date.now() - new Date(timestamp).getTime()) / (1_000 * 60 * 60 * 24);
35
+ }
36
+ function recencyDecay(timestamp, decayRate) {
37
+ const lambda = DECAY_LAMBDAS[decayRate] ?? DECAY_LAMBDAS.medium;
38
+ return Math.exp(-lambda * ageInDays(timestamp));
39
+ }
40
+ function rowToFact(row) {
41
+ return {
42
+ id: row.id,
43
+ subject: row.subject,
44
+ predicate: row.predicate,
45
+ object: row.object,
46
+ confidence: row.confidence,
47
+ decayRate: row.decay_rate,
48
+ timestamp: new Date(row.timestamp),
49
+ sourceSessionId: row.source_session_id,
50
+ ...(row.source_session_label !== null ? { sourceSessionLabel: row.source_session_label } : {}),
51
+ ...(row.context !== null ? { context: row.context } : {}),
52
+ };
53
+ }
54
+ /**
55
+ * Merge two ranked lists via Reciprocal Rank Fusion.
56
+ */
57
+ function rrf(vecRanked, bm25Ranked) {
58
+ const scores = new Map();
59
+ for (let rank = 0; rank < vecRanked.length; rank++) {
60
+ const id = vecRanked[rank]?.[0];
61
+ if (id === undefined)
62
+ continue;
63
+ scores.set(id, (scores.get(id) ?? 0) + 1 / (RRF_K + rank + 1));
64
+ }
65
+ for (let rank = 0; rank < bm25Ranked.length; rank++) {
66
+ const id = bm25Ranked[rank]?.[0];
67
+ if (id === undefined)
68
+ continue;
69
+ scores.set(id, (scores.get(id) ?? 0) + 1 / (RRF_K + rank + 1));
70
+ }
71
+ return scores;
72
+ }
73
+ // ─── Public API ───────────────────────────────────────────────────────────────
74
+ /**
75
+ * Hybrid search over facts.
76
+ *
77
+ * @param db The better-sqlite3 Database instance (with sqlite-vec loaded)
78
+ * @param userId Scopes the search to this user's data
79
+ * @param query Natural language query string
80
+ * @param limit Number of results to return (default 20)
81
+ */
82
+ export async function searchFacts(db, userId, query, limit = 20) {
83
+ // ── 1. Fetch all non-deleted fact rows for this user ────────────────────
84
+ const allRows = db
85
+ .prepare(`SELECT id, user_id, subject, predicate, object, confidence,
86
+ decay_rate, timestamp, source_session_id, source_session_label,
87
+ context, deleted_at
88
+ FROM facts
89
+ WHERE user_id = ? AND deleted_at IS NULL
90
+ ORDER BY timestamp DESC`)
91
+ .all(userId);
92
+ if (allRows.length === 0)
93
+ return [];
94
+ const idToRow = new Map(allRows.map((r) => [r.id, r]));
95
+ // ── 2. BM25 search ───────────────────────────────────────────────────────
96
+ const corpus = allRows.map(factText);
97
+ const bm25 = new Bm25(corpus);
98
+ const bm25RawScores = bm25.scores(query);
99
+ const bm25Ranked = allRows
100
+ .map((r, i) => [r.id, bm25RawScores[i] ?? 0])
101
+ .sort((a, b) => b[1] - a[1]);
102
+ // ── 3. Vector search via sqlite-vec ─────────────────────────────────────
103
+ const queryVec = await embedQuery(query);
104
+ const queryBlob = Buffer.from(queryVec.buffer);
105
+ const vecFetchLimit = Math.min(allRows.length, Math.max(RERANK_TOP_K * 2, limit * 3, 50));
106
+ const vecRows = db
107
+ .prepare(`SELECT rowid, distance FROM vec_facts
108
+ WHERE embedding MATCH ?
109
+ ORDER BY distance
110
+ LIMIT ?`)
111
+ .all(queryBlob, vecFetchLimit);
112
+ const vecRanked = [];
113
+ for (const vecRow of vecRows) {
114
+ const factRow = db
115
+ .prepare(`SELECT id FROM facts WHERE vec_rowid = ? AND user_id = ? AND deleted_at IS NULL`)
116
+ .get(vecRow.rowid, userId);
117
+ if (factRow !== undefined) {
118
+ vecRanked.push([factRow.id, 1 - vecRow.distance]);
119
+ }
120
+ }
121
+ // ── 4. RRF merge ────────────────────────────────────────────────────────
122
+ const rrfScores = rrf(vecRanked, bm25Ranked);
123
+ // ── 5. Recency decay (per-fact lambda) ──────────────────────────────────
124
+ const decayedScores = [];
125
+ for (const [id, rrfScore] of rrfScores) {
126
+ const row = idToRow.get(id);
127
+ if (row === undefined)
128
+ continue;
129
+ const decay = recencyDecay(row.timestamp, row.decay_rate);
130
+ decayedScores.push([id, rrfScore * decay]);
131
+ }
132
+ decayedScores.sort((a, b) => b[1] - a[1]);
133
+ // ── 6. Take top candidates for reranking ────────────────────────────────
134
+ const candidates = decayedScores.slice(0, Math.max(RERANK_TOP_K, limit));
135
+ // ── 7. Cross-encoder reranking ──────────────────────────────────────────
136
+ const passages = candidates.map(([id]) => factText(idToRow.get(id)));
137
+ const rerankerScores = await rerankScores(query, passages);
138
+ const hasRerankerSignal = rerankerScores.some((s) => s !== 0);
139
+ const ranked = candidates.map(([id, rrfDecayScore], i) => ({
140
+ id,
141
+ finalScore: hasRerankerSignal ? (rerankerScores[i] ?? 0) : rrfDecayScore,
142
+ }));
143
+ ranked.sort((a, b) => b.finalScore - a.finalScore);
144
+ // ── 8. Build output ─────────────────────────────────────────────────────
145
+ return ranked.slice(0, limit).map(({ id, finalScore }) => {
146
+ const row = idToRow.get(id);
147
+ const fact = rowToFact(row);
148
+ return {
149
+ fact,
150
+ score: finalScore,
151
+ ageInDays: ageInDays(row.timestamp),
152
+ };
153
+ });
154
+ }
155
+ //# sourceMappingURL=fact-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-search.js","sourceRoot":"","sources":["../src/fact-search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export type { Fact, IngestResult, MessageExchange, SearchResult, StoreStatus } from './types.js';
2
+ export { DecayRate } from './types.js';
3
+ export type { MemoryStore } from './store.js';
4
+ export type { RawLogChunk, ScoreResult } from './scorer.js';
5
+ export { computeDecay, scoreFact, scoreRawLog } from './scorer.js';
6
+ export { LocalStore } from './local-store.js';
7
+ export type { LocalStoreOptions, RawLogSearchResult, RawFact, RawLogEntry, ExportData } from './local-store.js';
8
+ export { extractFacts } from './extractor.js';
9
+ export { callLLM, resolveAnthropicKey } from './llm-client.js';
10
+ export { embed, embedQuery, rerankScores, EMBED_DIM } from './embedder.js';
11
+ export { Bm25, tokenize } from './bm25.js';
12
+ export { chunkExchange, formatExchange, CHUNK_WORDS, OVERLAP_WORDS } from './chunker.js';
13
+ export type { Chunk } from './chunker.js';
14
+ export { searchRawLog } from './raw-log-search.js';
15
+ export type { ScoredFact, RawChunk, MemoryContext, ReadPathOptions, ReadPathStore } from './read-path.js';
16
+ export { buildMemoryContext } from './read-path.js';
17
+ export { formatContextBlock, formatAge } from './context-builder.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACzF,YAAY,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export { DecayRate } from './types.js';
2
+ export { computeDecay, scoreFact, scoreRawLog } from './scorer.js';
3
+ export { LocalStore } from './local-store.js';
4
+ export { extractFacts } from './extractor.js';
5
+ export { callLLM, resolveAnthropicKey } from './llm-client.js';
6
+ export { embed, embedQuery, rerankScores, EMBED_DIM } from './embedder.js';
7
+ export { Bm25, tokenize } from './bm25.js';
8
+ export { chunkExchange, formatExchange, CHUNK_WORDS, OVERLAP_WORDS } from './chunker.js';
9
+ export { searchRawLog } from './raw-log-search.js';
10
+ export { buildMemoryContext } from './read-path.js';
11
+ export { formatContextBlock, formatAge } from './context-builder.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzF,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Resolve the OpenAI API key from environment variables.
3
+ * Returns the key if found, otherwise throws a clear error.
4
+ *
5
+ * Set OPENAI_API_KEY in your environment before using Plumb with the OpenAI provider.
6
+ *
7
+ * @throws Error if no key is found
8
+ */
9
+ export declare function resolveOpenAIKey(): string;
10
+ /**
11
+ * Resolve the Anthropic API key from environment variables.
12
+ * Returns the key if found, otherwise throws a clear error.
13
+ *
14
+ * Set ANTHROPIC_API_KEY in your environment before using Plumb with the Anthropic provider.
15
+ *
16
+ * @throws Error if no key is found
17
+ */
18
+ export declare function resolveAnthropicKey(): string;
19
+ /**
20
+ * Calls the configured LLM with the given prompt and returns the text response.
21
+ * Provider and model are configurable via env:
22
+ * PLUMB_LLM_PROVIDER — 'openai' (default), 'anthropic', 'ollama', 'openai-compatible'
23
+ * PLUMB_LLM_MODEL — model ID, defaults vary per provider
24
+ * PLUMB_LLM_BASE_URL — for 'openai-compatible' provider
25
+ * OLLAMA_HOST — for 'ollama' provider (default: http://localhost:11434/v1)
26
+ */
27
+ export declare function callLLM(prompt: string): Promise<string>;
28
+ //# sourceMappingURL=llm-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-client.d.ts","sourceRoot":"","sources":["../src/llm-client.ts"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAQzC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAQ5C;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4E7D"}
@@ -0,0 +1,115 @@
1
+ import OpenAI from 'openai';
2
+ /**
3
+ * Default models per provider
4
+ */
5
+ const DEFAULT_MODELS = {
6
+ openai: 'gpt-4o-mini',
7
+ anthropic: 'claude-haiku-4-5-20251001',
8
+ ollama: 'llama3.1',
9
+ 'openai-compatible': 'gpt-4o-mini',
10
+ };
11
+ /**
12
+ * Resolve the OpenAI API key from environment variables.
13
+ * Returns the key if found, otherwise throws a clear error.
14
+ *
15
+ * Set OPENAI_API_KEY in your environment before using Plumb with the OpenAI provider.
16
+ *
17
+ * @throws Error if no key is found
18
+ */
19
+ export function resolveOpenAIKey() {
20
+ const envKey = process.env['OPENAI_API_KEY'];
21
+ if (envKey)
22
+ return envKey;
23
+ throw new Error('Plumb fact extraction requires OPENAI_API_KEY. ' +
24
+ 'Set the OPENAI_API_KEY environment variable and try again.');
25
+ }
26
+ /**
27
+ * Resolve the Anthropic API key from environment variables.
28
+ * Returns the key if found, otherwise throws a clear error.
29
+ *
30
+ * Set ANTHROPIC_API_KEY in your environment before using Plumb with the Anthropic provider.
31
+ *
32
+ * @throws Error if no key is found
33
+ */
34
+ export function resolveAnthropicKey() {
35
+ const envKey = process.env['ANTHROPIC_API_KEY'];
36
+ if (envKey)
37
+ return envKey;
38
+ throw new Error('Plumb fact extraction requires ANTHROPIC_API_KEY. ' +
39
+ 'Set the ANTHROPIC_API_KEY environment variable and try again.');
40
+ }
41
+ /**
42
+ * Calls the configured LLM with the given prompt and returns the text response.
43
+ * Provider and model are configurable via env:
44
+ * PLUMB_LLM_PROVIDER — 'openai' (default), 'anthropic', 'ollama', 'openai-compatible'
45
+ * PLUMB_LLM_MODEL — model ID, defaults vary per provider
46
+ * PLUMB_LLM_BASE_URL — for 'openai-compatible' provider
47
+ * OLLAMA_HOST — for 'ollama' provider (default: http://localhost:11434/v1)
48
+ */
49
+ export async function callLLM(prompt) {
50
+ const provider = process.env['PLUMB_LLM_PROVIDER'] ?? 'openai';
51
+ const model = process.env['PLUMB_LLM_MODEL'] ?? DEFAULT_MODELS[provider] ?? 'gpt-4o-mini';
52
+ if (provider === 'openai') {
53
+ const apiKey = resolveOpenAIKey();
54
+ const client = new OpenAI({ apiKey });
55
+ const response = await client.chat.completions.create({
56
+ model,
57
+ messages: [{ role: 'user', content: prompt }],
58
+ max_tokens: 4096,
59
+ });
60
+ return response.choices[0]?.message?.content ?? '';
61
+ }
62
+ if (provider === 'anthropic') {
63
+ // Dynamic import to handle optional dependency
64
+ let Anthropic;
65
+ try {
66
+ Anthropic = (await import('@anthropic-ai/sdk')).default;
67
+ }
68
+ catch {
69
+ throw new Error('Anthropic provider requires @anthropic-ai/sdk. Install it with: npm install @anthropic-ai/sdk');
70
+ }
71
+ const apiKey = resolveAnthropicKey();
72
+ const client = new Anthropic({ apiKey });
73
+ const message = await client.messages.create({
74
+ model,
75
+ max_tokens: 4096,
76
+ messages: [{ role: 'user', content: prompt }],
77
+ });
78
+ const block = message.content[0];
79
+ if (block === undefined || block.type !== 'text') {
80
+ throw new Error('Unexpected response type from Anthropic LLM');
81
+ }
82
+ return block.text;
83
+ }
84
+ if (provider === 'ollama') {
85
+ // Ollama provides an OpenAI-compatible API
86
+ const baseURL = process.env['OLLAMA_HOST'] ?? 'http://localhost:11434/v1';
87
+ const client = new OpenAI({
88
+ baseURL,
89
+ apiKey: 'ollama', // Required by openai package but ignored by Ollama
90
+ });
91
+ const response = await client.chat.completions.create({
92
+ model,
93
+ messages: [{ role: 'user', content: prompt }],
94
+ max_tokens: 4096,
95
+ });
96
+ return response.choices[0]?.message?.content ?? '';
97
+ }
98
+ if (provider === 'openai-compatible') {
99
+ const baseURL = process.env['PLUMB_LLM_BASE_URL'];
100
+ if (!baseURL) {
101
+ throw new Error('PLUMB_LLM_BASE_URL is required for openai-compatible provider. ' +
102
+ 'Example: export PLUMB_LLM_BASE_URL=https://api.together.xyz/v1');
103
+ }
104
+ const apiKey = resolveOpenAIKey();
105
+ const client = new OpenAI({ baseURL, apiKey });
106
+ const response = await client.chat.completions.create({
107
+ model,
108
+ messages: [{ role: 'user', content: prompt }],
109
+ max_tokens: 4096,
110
+ });
111
+ return response.choices[0]?.message?.content ?? '';
112
+ }
113
+ throw new Error(`Unsupported PLUMB_LLM_PROVIDER: ${provider}. Supported: openai, anthropic, ollama, openai-compatible`);
114
+ }
115
+ //# sourceMappingURL=llm-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-client.js","sourceRoot":"","sources":["../src/llm-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import Database from 'better-sqlite3';
2
+ import type { MemoryStore } from './store.js';
3
+ import type { Fact, IngestResult, MessageExchange, SearchResult, StoreStatus } from './types.js';
4
+ import { type RawLogSearchResult } from './raw-log-search.js';
5
+ export type { RawLogSearchResult };
6
+ export interface RawFact {
7
+ readonly id: string;
8
+ readonly userId: string;
9
+ readonly subject: string;
10
+ readonly predicate: string;
11
+ readonly object: string;
12
+ readonly confidence: number;
13
+ readonly decayRate: string;
14
+ readonly timestamp: string;
15
+ readonly sourceSessionId: string;
16
+ readonly sourceSessionLabel: string | null;
17
+ readonly context: string | null;
18
+ readonly deleted: boolean;
19
+ readonly deletedAt: string | null;
20
+ }
21
+ export interface RawLogEntry {
22
+ readonly id: string;
23
+ readonly userId: string;
24
+ readonly sessionId: string;
25
+ readonly sessionLabel: string | null;
26
+ readonly userMessage: string;
27
+ readonly agentResponse: string;
28
+ readonly timestamp: string;
29
+ readonly source: string;
30
+ readonly chunkText: string;
31
+ readonly chunkIndex: number;
32
+ readonly contentHash: string | null;
33
+ }
34
+ export interface ExportData {
35
+ readonly facts: readonly RawFact[];
36
+ readonly rawLog: readonly RawLogEntry[];
37
+ }
38
+ export interface LocalStoreOptions {
39
+ /** Absolute path to the SQLite database file. Defaults to ~/.plumb/memory.db */
40
+ dbPath?: string;
41
+ /** User ID for scoping all data. Defaults to 'default' (single-user local install). */
42
+ userId?: string;
43
+ }
44
+ export declare class LocalStore implements MemoryStore {
45
+ #private;
46
+ /** Expose database for plugin use (e.g., NudgeManager) */
47
+ get db(): Database.Database;
48
+ /** Expose userId for plugin use */
49
+ get userId(): string;
50
+ constructor(options?: LocalStoreOptions);
51
+ store(fact: Omit<Fact, 'id'>): Promise<string>;
52
+ search(query: string, limit?: number): Promise<readonly SearchResult[]>;
53
+ delete(id: string): Promise<void>;
54
+ status(): Promise<StoreStatus>;
55
+ ingest(exchange: MessageExchange): Promise<IngestResult>;
56
+ /**
57
+ * Hybrid search over raw_log (Layer 1 retrieval).
58
+ * See raw-log-search.ts for the full pipeline description.
59
+ */
60
+ searchRawLog(query: string, limit?: number): Promise<readonly RawLogSearchResult[]>;
61
+ /**
62
+ * Wait for all in-flight fact extractions to complete.
63
+ * Call this before close() to ensure all async work is done.
64
+ */
65
+ drain(): Promise<void>;
66
+ /**
67
+ * Re-extract facts for orphaned raw_log chunks (chunks with no corresponding facts).
68
+ *
69
+ * This is useful when fact extraction failed during initial ingest (e.g., missing API key,
70
+ * rate limits, crashes). Re-running the normal seeder won't help because content-hash dedup
71
+ * skips already-ingested chunks before reaching the extraction phase.
72
+ *
73
+ * This method directly calls extractFacts() for each orphaned chunk, bypassing the dedup gate.
74
+ *
75
+ * @param throttleMs - Delay between extractions (default 1000ms) to stay under rate limits
76
+ * @returns Statistics: orphansFound, factsCreated
77
+ */
78
+ reextractOrphans(throttleMs?: number): Promise<{
79
+ orphansFound: number;
80
+ factsCreated: number;
81
+ }>;
82
+ /**
83
+ * Get top subjects by fact count (for plumb status command).
84
+ * Returns subjects ordered by number of facts (non-deleted only).
85
+ */
86
+ topSubjects(userId: string, limit?: number): Array<{
87
+ subject: string;
88
+ count: number;
89
+ }>;
90
+ /**
91
+ * Export all data for a user (for plumb export command).
92
+ * Returns raw database rows (no vector data).
93
+ * Includes soft-deleted facts for transparency.
94
+ */
95
+ exportAll(userId: string): ExportData;
96
+ /** Close the database connection. Call when done (e.g. in tests). */
97
+ close(): void;
98
+ }
99
+ //# sourceMappingURL=local-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-store.d.ts","sourceRoot":"","sources":["../src/local-store.ts"],"names":[],"mappings":"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"}