mnueron 0.2.0 → 0.4.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.
@@ -0,0 +1,292 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // P3 — Relationship extraction.
3
+ //
4
+ // Given the content of a memory and the canonical entities P1+P2.3 already
5
+ // resolved for it, this module asks an LLM to extract the *relationships*
6
+ // between those entities. The result populates a `relations` table that
7
+ // turns the memory store into a queryable knowledge graph:
8
+ //
9
+ // "John recommended deprecating the v1 API after the Q3 review"
10
+ // → (John) —[recommended]→ (deprecate v1 API)
11
+ // (recommendation) —[came_from]→ (Q3 review)
12
+ //
13
+ // P4 — Bi-temporal extension lives in this same module. The same Haiku
14
+ // call is asked to also extract `valid_from` / `valid_to` windows where
15
+ // they're stated in the text ("worked at Stripe 2022 - April 2025"). When
16
+ // the text doesn't say, both fields stay null.
17
+ //
18
+ // Design echoes entity-extractor.ts:
19
+ // - raw fetch (no SDK dep in the CLI bundle)
20
+ // - BYOK precedence: metadata.byok_anthropic_key > env ANTHROPIC_API_KEY > env OPENAI_API_KEY
21
+ // - fail-open: returns [] on any error
22
+ // - cap the output so an over-eager LLM can't flood the graph
23
+ //
24
+ // IMPORTANT — TIER GATING (LOCAL vs HOSTED):
25
+ // This module is the LOCAL implementation. It falls through to env keys
26
+ // (ANTHROPIC_API_KEY / OPENAI_API_KEY) unconditionally because the operator
27
+ // IS the user — those keys live on their machine. If/when this is mirrored
28
+ // to the hosted backend (ai-boilerplate-pro), the env fallback MUST be
29
+ // gated by an `allowServerKey` flag set per-org based on plan tier:
30
+ //
31
+ // const apiKey = byokKey || (allowServerKey ? process.env.ANTHROPIC_API_KEY : undefined);
32
+ //
33
+ // Free-tier hosted orgs without BYOK should get graceful no-op (return []),
34
+ // not consume the server's LLM budget. The hosted entity-resolver.ts
35
+ // already implements this pattern — match it.
36
+ //
37
+ // Why a separate module from entity-extractor: it gets called LATER in the
38
+ // save path (after resolution), it sees the resolved canonical IDs, and
39
+ // the prompt is materially different. Sharing one module would tangle the
40
+ // gating logic.
41
+ // ─────────────────────────────────────────────────────────────────────────────
42
+ const ANTHROPIC_MODEL = 'claude-haiku-4-5';
43
+ const OPENAI_MODEL = 'gpt-4o-mini';
44
+ const MAX_CONTENT_CHARS = 12000;
45
+ const MAX_RELATIONS = 25;
46
+ const TIMEOUT_MS = 30000;
47
+ /**
48
+ * Returns true when we should run relationship extraction for a memory.
49
+ * Requires at least TWO resolved entities (a relation needs both endpoints)
50
+ * and an explicit opt-in via metadata or the global env var. We also gate
51
+ * on content length to keep noise out of the graph.
52
+ */
53
+ export function shouldExtractRelations(contentLen, entityCount, metadata) {
54
+ if (entityCount < 2)
55
+ return false;
56
+ if (contentLen < 80)
57
+ return false; // skimpy memories rarely have real relations
58
+ if (metadata?.extract_relations === true)
59
+ return true;
60
+ const a = metadata?.byok_anthropic_key;
61
+ if (typeof a === 'string' && a.length > 0)
62
+ return true;
63
+ const o = metadata?.byok_openai_key;
64
+ if (typeof o === 'string' && o.length > 0)
65
+ return true;
66
+ if ((process.env.MNUERON_ENABLE_RELATION_EXTRACTION ?? '').toLowerCase() === 'true')
67
+ return true;
68
+ return false;
69
+ }
70
+ /**
71
+ * Pull structured relations between resolved entities. Returns [] on any
72
+ * error. Caller is responsible for inserting the rows into `relations`.
73
+ */
74
+ export async function extractRelations(content, entities, opts = {}) {
75
+ if (!content || entities.length < 2)
76
+ return [];
77
+ const trimmed = content.slice(0, MAX_CONTENT_CHARS);
78
+ try {
79
+ if (opts.anthropicKey) {
80
+ const out = await extractViaAnthropic(trimmed, entities, opts.anthropicKey);
81
+ if (out.length > 0)
82
+ return cap(out);
83
+ }
84
+ if (opts.openaiKey) {
85
+ const out = await extractViaOpenAI(trimmed, entities, opts.openaiKey);
86
+ if (out.length > 0)
87
+ return cap(out);
88
+ }
89
+ if (process.env.ANTHROPIC_API_KEY) {
90
+ const out = await extractViaAnthropic(trimmed, entities, process.env.ANTHROPIC_API_KEY);
91
+ if (out.length > 0)
92
+ return cap(out);
93
+ }
94
+ if (process.env.OPENAI_API_KEY) {
95
+ const out = await extractViaOpenAI(trimmed, entities, process.env.OPENAI_API_KEY);
96
+ if (out.length > 0)
97
+ return cap(out);
98
+ }
99
+ }
100
+ catch (e) {
101
+ console.warn('[mnueron/relation-extractor]', e instanceof Error ? e.message : e);
102
+ }
103
+ return [];
104
+ }
105
+ const SYSTEM_PROMPT = [
106
+ 'You extract structured relationships between named entities from a memory',
107
+ "text that will be stored alongside an AI agent's long-term knowledge graph.",
108
+ '',
109
+ 'The user message has two parts:',
110
+ ' 1. CONTENT — the source text',
111
+ ' 2. ENTITIES — a numbered list of canonical entities ALREADY identified',
112
+ ' in this text. Each has an id, name, and type.',
113
+ '',
114
+ 'Your task: for every relationship the text asserts BETWEEN those entities,',
115
+ 'emit one row. You may also emit a row pointing to a NEW entity by using',
116
+ 'name + type (and leaving id null) — but prefer matching existing ids when',
117
+ 'possible.',
118
+ '',
119
+ 'OUTPUT — STRICT JSON, no prose, no markdown. Schema:',
120
+ '{',
121
+ ' "relations": [',
122
+ ' {',
123
+ ' "from_id": "<entity id from ENTITIES list>",',
124
+ ' "to_id": "<entity id from ENTITIES list>",',
125
+ ' "predicate": "<lowercase snake_case verb phrase>",',
126
+ ' "confidence": <0.0 - 1.0>,',
127
+ ' "valid_from": "<ISO 8601 date | null>",',
128
+ ' "valid_to": "<ISO 8601 date | null>"',
129
+ ' }',
130
+ ' ]',
131
+ '}',
132
+ '',
133
+ 'Rules:',
134
+ ' - predicate: short, lowercase, snake_case. Use verbs that capture the',
135
+ ' semantic. Examples: recommended, works_at, deprecated_for, decided_by,',
136
+ ' reports_to, founded, mentioned_in, blocked_by.',
137
+ ' - Skip pronoun-only and trivial relations.',
138
+ ' - If a temporal window is stated ("from March 2022 to April 2025"),',
139
+ ' fill valid_from/valid_to as ISO dates. If only a start is given,',
140
+ ' leave valid_to null. If neither is stated, both null.',
141
+ ' - Skip relations you are not confident about (confidence < 0.5).',
142
+ ' - Cap output at 25 relations.',
143
+ ].join('\n');
144
+ function buildUserMessage(content, entities) {
145
+ const list = entities
146
+ .map((e, i) => ` ${i + 1}. id=${e.canonical_id} name="${e.name}" type=${e.type}`)
147
+ .join('\n');
148
+ return `CONTENT:\n${content}\n\nENTITIES:\n${list}`;
149
+ }
150
+ async function extractViaAnthropic(content, entities, apiKey) {
151
+ const ctrl = new AbortController();
152
+ const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
153
+ try {
154
+ const resp = await fetch('https://api.anthropic.com/v1/messages', {
155
+ method: 'POST',
156
+ headers: {
157
+ 'x-api-key': apiKey,
158
+ 'anthropic-version': '2023-06-01',
159
+ 'Content-Type': 'application/json',
160
+ },
161
+ body: JSON.stringify({
162
+ model: ANTHROPIC_MODEL,
163
+ max_tokens: 1500,
164
+ temperature: 0.0,
165
+ system: SYSTEM_PROMPT,
166
+ messages: [
167
+ { role: 'user', content: buildUserMessage(content, entities) },
168
+ ],
169
+ }),
170
+ signal: ctrl.signal,
171
+ });
172
+ if (!resp.ok)
173
+ return [];
174
+ const data = (await resp.json());
175
+ const text = (data.content ?? [])
176
+ .filter((b) => b.type === 'text')
177
+ .map((b) => b.text ?? '')
178
+ .join('');
179
+ return parseRelations(text, entities);
180
+ }
181
+ catch (e) {
182
+ console.warn('[mnueron/relation-extractor/anthropic]', e instanceof Error ? e.message : e);
183
+ return [];
184
+ }
185
+ finally {
186
+ clearTimeout(timer);
187
+ }
188
+ }
189
+ async function extractViaOpenAI(content, entities, apiKey) {
190
+ const ctrl = new AbortController();
191
+ const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
192
+ try {
193
+ const resp = await fetch('https://api.openai.com/v1/chat/completions', {
194
+ method: 'POST',
195
+ headers: {
196
+ Authorization: 'Bearer ' + apiKey,
197
+ 'Content-Type': 'application/json',
198
+ },
199
+ body: JSON.stringify({
200
+ model: OPENAI_MODEL,
201
+ max_tokens: 1500,
202
+ temperature: 0.0,
203
+ response_format: { type: 'json_object' },
204
+ messages: [
205
+ { role: 'system', content: SYSTEM_PROMPT },
206
+ { role: 'user', content: buildUserMessage(content, entities) },
207
+ ],
208
+ }),
209
+ signal: ctrl.signal,
210
+ });
211
+ if (!resp.ok)
212
+ return [];
213
+ const data = (await resp.json());
214
+ return parseRelations(data.choices?.[0]?.message?.content ?? '', entities);
215
+ }
216
+ catch (e) {
217
+ console.warn('[mnueron/relation-extractor/openai]', e instanceof Error ? e.message : e);
218
+ return [];
219
+ }
220
+ finally {
221
+ clearTimeout(timer);
222
+ }
223
+ }
224
+ /** Resilient JSON parse + validation. Drops any row whose from_id / to_id
225
+ * isn't in the provided entities list — keeps the graph honest. */
226
+ function parseRelations(raw, entities) {
227
+ if (!raw)
228
+ return [];
229
+ let s = raw.trim();
230
+ if (s.startsWith('```')) {
231
+ s = s.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '');
232
+ }
233
+ const start = s.search(/[\[{]/);
234
+ if (start > 0)
235
+ s = s.slice(start);
236
+ let parsed;
237
+ try {
238
+ parsed = JSON.parse(s);
239
+ }
240
+ catch {
241
+ return [];
242
+ }
243
+ const arr = Array.isArray(parsed) ? parsed :
244
+ parsed && typeof parsed === 'object' && Array.isArray(parsed.relations)
245
+ ? parsed.relations
246
+ : [];
247
+ const validIds = new Set(entities.map((e) => e.canonical_id));
248
+ const out = [];
249
+ for (const raw of arr) {
250
+ if (!raw || typeof raw !== 'object')
251
+ continue;
252
+ const r = raw;
253
+ const from_id = typeof r.from_id === 'string' ? r.from_id : '';
254
+ const to_id = typeof r.to_id === 'string' ? r.to_id : '';
255
+ if (!validIds.has(from_id) || !validIds.has(to_id))
256
+ continue;
257
+ if (from_id === to_id)
258
+ continue; // self-loops are noise
259
+ const predicate = typeof r.predicate === 'string'
260
+ ? r.predicate.trim().toLowerCase().replace(/\s+/g, '_')
261
+ : '';
262
+ if (!predicate)
263
+ continue;
264
+ const confidence = typeof r.confidence === 'number'
265
+ ? clamp01(r.confidence)
266
+ : 0.7;
267
+ if (confidence < 0.5)
268
+ continue;
269
+ out.push({
270
+ from_canonical_id: from_id,
271
+ to_canonical_id: to_id,
272
+ predicate,
273
+ confidence,
274
+ valid_from: parseIsoDate(r.valid_from),
275
+ valid_to: parseIsoDate(r.valid_to),
276
+ });
277
+ }
278
+ return out;
279
+ }
280
+ function parseIsoDate(v) {
281
+ if (typeof v !== 'string' || !v)
282
+ return null;
283
+ const t = Date.parse(v);
284
+ return Number.isFinite(t) ? t : null;
285
+ }
286
+ function clamp01(n) {
287
+ return n < 0 ? 0 : n > 1 ? 1 : n;
288
+ }
289
+ function cap(items) {
290
+ return items.length > MAX_RELATIONS ? items.slice(0, MAX_RELATIONS) : items;
291
+ }
292
+ //# sourceMappingURL=relation-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relation-extractor.js","sourceRoot":"","sources":["../../src/store/relation-extractor.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gCAAgC;AAChC,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,kEAAkE;AAClE,iDAAiD;AACjD,kDAAkD;AAClD,EAAE;AACF,uEAAuE;AACvE,wEAAwE;AACxE,0EAA0E;AAC1E,+CAA+C;AAC/C,EAAE;AACF,qCAAqC;AACrC,+CAA+C;AAC/C,gGAAgG;AAChG,yCAAyC;AACzC,gEAAgE;AAChE,EAAE;AACF,6CAA6C;AAC7C,wEAAwE;AACxE,4EAA4E;AAC5E,2EAA2E;AAC3E,uEAAuE;AACvE,oEAAoE;AACpE,EAAE;AACF,4FAA4F;AAC5F,EAAE;AACF,4EAA4E;AAC5E,qEAAqE;AACrE,8CAA8C;AAC9C,EAAE;AACF,2EAA2E;AAC3E,wEAAwE;AACxE,0EAA0E;AAC1E,gBAAgB;AAChB,gFAAgF;AAEhF,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAC3C,MAAM,YAAY,GAAG,aAAa,CAAC;AACnC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAChC,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,MAAM,UAAU,GAAG,KAAK,CAAC;AAiCzB;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAkB,EAClB,WAAmB,EACnB,QAA6C;IAE7C,IAAI,WAAW,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClC,IAAI,UAAU,GAAG,EAAE;QAAE,OAAO,KAAK,CAAC,CAAC,6CAA6C;IAChF,IAAI,QAAQ,EAAE,iBAAiB,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,CAAC,GAAG,QAAQ,EAAE,kBAAkB,CAAC;IACvC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,CAAC,GAAG,QAAQ,EAAE,eAAe,CAAC;IACpC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACjG,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAe,EACf,QAAqC,EACrC,OAAuB,EAAE;IAEzB,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAEpD,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,MAAM,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YAC5E,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YACtE,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,MAAM,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;YACxF,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YAClF,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CACV,8BAA8B,EAC9B,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CACnC,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,aAAa,GAAG;IACpB,2EAA2E;IAC3E,6EAA6E;IAC7E,EAAE;IACF,iCAAiC;IACjC,gCAAgC;IAChC,0EAA0E;IAC1E,+DAA+D;IAC/D,EAAE;IACF,4EAA4E;IAC5E,yEAAyE;IACzE,2EAA2E;IAC3E,WAAW;IACX,EAAE;IACF,sDAAsD;IACtD,GAAG;IACH,kBAAkB;IAClB,OAAO;IACP,oDAAoD;IACpD,oDAAoD;IACpD,0DAA0D;IAC1D,kCAAkC;IAClC,+CAA+C;IAC/C,8CAA8C;IAC9C,OAAO;IACP,KAAK;IACL,GAAG;IACH,EAAE;IACF,QAAQ;IACR,yEAAyE;IACzE,4EAA4E;IAC5E,oDAAoD;IACpD,8CAA8C;IAC9C,uEAAuE;IACvE,sEAAsE;IACtE,2DAA2D;IAC3D,oEAAoE;IACpE,iCAAiC;CAClC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,SAAS,gBAAgB,CACvB,OAAe,EACf,QAAqC;IAErC,MAAM,IAAI,GAAG,QAAQ;SAClB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,YAAY,WAAW,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC;SACnF,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,aAAa,OAAO,kBAAkB,IAAI,EAAE,CAAC;AACtD,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,OAAe,EACf,QAAqC,EACrC,MAAc;IAEd,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;IACzD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,uCAAuC,EAAE;YAChE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,WAAW,EAAE,MAAM;gBACnB,mBAAmB,EAAE,YAAY;gBACjC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK,EAAE,eAAe;gBACtB,UAAU,EAAE,IAAI;gBAChB,WAAW,EAAE,GAAG;gBAChB,MAAM,EAAE,aAAa;gBACrB,QAAQ,EAAE;oBACR,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE;iBAC/D;aACF,CAAC;YACF,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAE9B,CAAC;QACF,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;aAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;aAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;aACxB,IAAI,CAAC,EAAE,CAAC,CAAC;QACZ,OAAO,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CACV,wCAAwC,EACxC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CACnC,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,OAAe,EACf,QAAqC,EACrC,MAAc;IAEd,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;IACzD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,4CAA4C,EAAE;YACrE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,SAAS,GAAG,MAAM;gBACjC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK,EAAE,YAAY;gBACnB,UAAU,EAAE,IAAI;gBAChB,WAAW,EAAE,GAAG;gBAChB,eAAe,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;gBACxC,QAAQ,EAAE;oBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE;oBAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE;iBAC/D;aACF,CAAC;YACF,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAE9B,CAAC;QACF,OAAO,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC7E,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CACV,qCAAqC,EACrC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CACnC,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED;oEACoE;AACpE,SAAS,cAAc,CACrB,GAAW,EACX,QAAqC;IAErC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,KAAK,GAAG,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAElC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IAC/B,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;IAEpB,MAAM,GAAG,GACP,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAE,MAAc,CAAC,SAAS,CAAC;YAC9E,CAAC,CAAE,MAAc,CAAC,SAAS;YAC3B,CAAC,CAAC,EAAE,CAAC;IAET,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,SAAS;QAC9C,MAAM,CAAC,GAAG,GAA8B,CAAC;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,KAAK,GAAK,OAAO,CAAC,CAAC,KAAK,KAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAG,CAAC,CAAC,EAAE,CAAC;QAC/D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,SAAS;QAC7D,IAAI,OAAO,KAAK,KAAK;YAAE,SAAS,CAAC,uBAAuB;QAExD,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;YAC/C,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;YACvD,CAAC,CAAC,EAAE,CAAC;QACP,IAAI,CAAC,SAAS;YAAE,SAAS;QAEzB,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ;YACjD,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;YACvB,CAAC,CAAC,GAAG,CAAC;QACR,IAAI,UAAU,GAAG,GAAG;YAAE,SAAS;QAE/B,GAAG,CAAC,IAAI,CAAC;YACP,iBAAiB,EAAE,OAAO;YAC1B,eAAe,EAAE,KAAK;YACtB,SAAS;YACT,UAAU;YACV,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC;YACtC,QAAQ,EAAI,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC;SACrC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,YAAY,CAAC,CAAU;IAC9B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvC,CAAC;AAED,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,GAAG,CAAC,KAA0B;IACrC,OAAO,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC9E,CAAC"}
@@ -1,11 +1,45 @@
1
+ import { chunkContent } from './chunking.js';
2
+ import { randomUUID } from 'node:crypto';
1
3
  /**
2
- * Remote HTTP provider talks to the hosted multi-tenant backend.
3
- * Multi-company isolation happens server-side via the token → org_id binding.
4
- * The MCP client doesn't need to know which org it's hitting; the token decides.
4
+ * The hosted backend is the Next.js app under `ai-boilerplate-pro`. Its API
5
+ * surface looks like:
6
+ *
7
+ * POST /api/memories - save one
8
+ * GET /api/memories?q=... [&ns=...] - search (BM25)
9
+ * GET /api/memories?namespace=... - list
10
+ * GET /api/memories/<id> - fetch one
11
+ * PATCH /api/memories/<id> - partial update
12
+ * DELETE /api/memories/<id> - remove
13
+ * POST /api/memories/search/bulk - multi-query search
14
+ * GET /api/namespaces - namespace + counts
15
+ * GET /api/health - health probe
16
+ *
17
+ * Notable hosted-side constraints (mirrored on the client so we don't
18
+ * burn round-trips discovering them):
19
+ *
20
+ * 1. No bulk-save endpoint. Every memory must POST /api/memories
21
+ * individually. We fan out with a small concurrency window.
22
+ * 2. 256KB per-memory content cap. Anything larger is 413'd. We
23
+ * pre-chunk via chunkContent() so each item fits.
24
+ * 3. 60 writes/min/token rate limit. A naive fan-out of hundreds of
25
+ * chunks will trip it; we throttle to ~1 write/sec and respect
26
+ * Retry-After on 429s.
27
+ * 4. Vercel ~4.5MB body limit. Single posts well under the 256KB cap
28
+ * stay comfortably below this.
29
+ *
30
+ * The /v1/* paths in older versions of this provider pointed at the
31
+ * unshipped standalone server in server/index.ts. If that ever deploys,
32
+ * give it its own provider class rather than overloading this one.
5
33
  */
34
+ const HOSTED_PER_MEMORY_CHAR_CAP = 256 * 1024;
35
+ const CHUNK_SOFT_TARGET = 4000;
36
+ const SAVE_CONCURRENCY = 2;
37
+ const MIN_WRITE_INTERVAL_MS = 1100;
38
+ const SAVE_MAX_RETRIES = 4;
6
39
  export class RemoteProvider {
7
40
  baseUrl;
8
41
  token;
42
+ lastWriteAt = 0;
9
43
  constructor(baseUrl, token) {
10
44
  this.baseUrl = baseUrl;
11
45
  this.token = token;
@@ -16,47 +50,175 @@ export class RemoteProvider {
16
50
  headers: {
17
51
  'Authorization': `Bearer ${this.token}`,
18
52
  'Content-Type': 'application/json',
19
- 'User-Agent': 'mnueron-mcp/0.1',
53
+ 'User-Agent': 'mnueron-mcp/0.2',
20
54
  },
21
55
  body: body ? JSON.stringify(body) : undefined,
22
56
  });
23
57
  if (!res.ok) {
24
58
  const text = await res.text().catch(() => '');
25
- throw new Error(`mnueron API ${res.status} ${method} ${path}: ${text}`);
59
+ const err = new Error(`mnueron API ${res.status} ${method} ${path}: ${text.slice(0, 500)}`);
60
+ err.status = res.status;
61
+ const ra = res.headers.get('retry-after');
62
+ if (ra) {
63
+ const n = Number(ra);
64
+ if (Number.isFinite(n))
65
+ err.retryAfterMs = n * 1000;
66
+ }
67
+ throw err;
26
68
  }
27
69
  if (res.status === 204)
28
70
  return undefined;
29
71
  return (await res.json());
30
72
  }
31
- save(input) {
32
- return this.req('POST', '/v1/memories', input);
73
+ async save(input) {
74
+ const content = input.content ?? '';
75
+ if (content.length > HOSTED_PER_MEMORY_CHAR_CAP || content.length > CHUNK_SOFT_TARGET * 2) {
76
+ await this.bulkSave([input]);
77
+ return {
78
+ id: '',
79
+ namespace: input.namespace,
80
+ content: input.content,
81
+ tags: input.tags ?? [],
82
+ source: input.source ?? 'manual',
83
+ source_ref: input.source_ref ?? null,
84
+ metadata: input.metadata ?? null,
85
+ created_at: Date.now(),
86
+ updated_at: Date.now(),
87
+ };
88
+ }
89
+ return this.saveOneThrottled(input);
90
+ }
91
+ async bulkSave(inputs) {
92
+ if (inputs.length === 0)
93
+ return { saved: 0, errors: 0 };
94
+ const expanded = [];
95
+ for (const it of inputs) {
96
+ const chunks = chunkContent(it.content ?? '');
97
+ if (chunks.length <= 1) {
98
+ expanded.push(it);
99
+ continue;
100
+ }
101
+ const parentRef = it.source_ref ?? `chunked:${randomUUID()}`;
102
+ const baseTags = it.tags ?? [];
103
+ const total = chunks.length;
104
+ for (let i = 0; i < total; i++) {
105
+ const c = chunks[i];
106
+ expanded.push({
107
+ content: c.content,
108
+ namespace: it.namespace,
109
+ tags: [...baseTags, 'chunk', ...(c.role ? [`role:${c.role}`] : [])],
110
+ source: it.source ?? 'manual',
111
+ source_ref: parentRef,
112
+ metadata: {
113
+ ...(it.metadata ?? {}),
114
+ parent_ref: parentRef,
115
+ chunk_index: i,
116
+ chunk_count: total,
117
+ ...(c.role ? { role: c.role } : {}),
118
+ },
119
+ });
120
+ }
121
+ }
122
+ let saved = 0;
123
+ let errors = 0;
124
+ let nextIdx = 0;
125
+ const total = expanded.length;
126
+ const worker = async () => {
127
+ while (true) {
128
+ const idx = nextIdx++;
129
+ if (idx >= total)
130
+ return;
131
+ const item = expanded[idx];
132
+ if (item.content && item.content.length > HOSTED_PER_MEMORY_CHAR_CAP) {
133
+ item.content = item.content.slice(0, HOSTED_PER_MEMORY_CHAR_CAP - 64) + '\n...[truncated]';
134
+ }
135
+ try {
136
+ await this.saveOneThrottled(item);
137
+ saved++;
138
+ if ((saved + errors) % 25 === 0 || saved + errors === total) {
139
+ process.stderr.write(`[mnueron] hosted save progress: ${saved + errors}/${total}\n`);
140
+ }
141
+ }
142
+ catch (e) {
143
+ errors++;
144
+ const msg = e instanceof Error ? e.message : String(e);
145
+ process.stderr.write(`[mnueron] save failed for chunk ${idx + 1}/${total}: ${msg.slice(0, 200)}\n`);
146
+ }
147
+ }
148
+ };
149
+ const workers = Array.from({ length: Math.min(SAVE_CONCURRENCY, total) }, () => worker());
150
+ await Promise.all(workers);
151
+ return { saved, errors };
33
152
  }
34
- bulkSave(inputs) {
35
- return this.req('POST', '/v1/memories/bulk', { items: inputs });
153
+ async saveOneThrottled(input) {
154
+ let attempt = 0;
155
+ while (true) {
156
+ const now = Date.now();
157
+ const earliest = this.lastWriteAt + MIN_WRITE_INTERVAL_MS;
158
+ if (now < earliest)
159
+ await sleep(earliest - now);
160
+ this.lastWriteAt = Date.now();
161
+ try {
162
+ return await this.req('POST', '/api/memories', input);
163
+ }
164
+ catch (e) {
165
+ const httpErr = e;
166
+ const status = httpErr?.status ?? 0;
167
+ const retryable = status === 429 || status >= 500;
168
+ if (!retryable || attempt >= SAVE_MAX_RETRIES)
169
+ throw e;
170
+ const backoff = httpErr?.retryAfterMs ?? Math.min(30_000, 1000 * Math.pow(2, attempt));
171
+ process.stderr.write(`[mnueron] ${status} - backing off ${backoff}ms (attempt ${attempt + 1}/${SAVE_MAX_RETRIES})\n`);
172
+ await sleep(backoff);
173
+ attempt++;
174
+ }
175
+ }
36
176
  }
37
- search(input) {
38
- return this.req('POST', '/v1/memories/search', input);
177
+ async search(input) {
178
+ const params = new URLSearchParams();
179
+ if (input.query)
180
+ params.set('q', input.query);
181
+ if (input.namespace)
182
+ params.set('namespace', input.namespace);
183
+ if (input.k != null)
184
+ params.set('limit', String(input.k));
185
+ return this.req('GET', `/api/memories?${params.toString()}`);
39
186
  }
40
- list(input) {
187
+ async list(input) {
41
188
  const params = new URLSearchParams();
42
189
  if (input.namespace)
43
190
  params.set('namespace', input.namespace);
44
191
  if (input.limit)
45
192
  params.set('limit', String(input.limit));
46
- if (input.before)
47
- params.set('before', String(input.before));
48
- return this.req('GET', `/v1/memories?${params}`);
193
+ return this.req('GET', `/api/memories?${params.toString()}`);
49
194
  }
50
- get(id) {
51
- return this.req('GET', `/v1/memories/${id}`);
195
+ async get(id) {
196
+ try {
197
+ return await this.req('GET', `/api/memories/${encodeURIComponent(id)}`);
198
+ }
199
+ catch (e) {
200
+ if (e?.status === 404)
201
+ return null;
202
+ throw e;
203
+ }
52
204
  }
53
205
  async delete(id) {
54
- await this.req('DELETE', `/v1/memories/${id}`);
55
- return true;
206
+ try {
207
+ await this.req('DELETE', `/api/memories/${encodeURIComponent(id)}`);
208
+ return true;
209
+ }
210
+ catch (e) {
211
+ if (e?.status === 404)
212
+ return false;
213
+ throw e;
214
+ }
56
215
  }
57
216
  namespaces() {
58
- return this.req('GET', '/v1/namespaces');
217
+ return this.req('GET', '/api/namespaces');
59
218
  }
60
219
  async close() { }
61
220
  }
221
+ function sleep(ms) {
222
+ return new Promise((resolve) => setTimeout(resolve, ms));
223
+ }
62
224
  //# sourceMappingURL=remote.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/store/remote.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,OAAO,cAAc;IAEf;IACA;IAFV,YACU,OAAe,EACf,KAAa;QADb,YAAO,GAAP,OAAO,CAAQ;QACf,UAAK,GAAL,KAAK,CAAQ;IACpB,CAAC;IAEI,KAAK,CAAC,GAAG,CAAI,MAAc,EAAE,IAAY,EAAE,IAAc;QAC/D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE;YAChD,MAAM;YACN,OAAO,EAAE;gBACP,eAAe,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;gBACvC,cAAc,EAAE,kBAAkB;gBAClC,YAAY,EAAE,iBAAiB;aAChC;YACD,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,eAAe,GAAG,CAAC,MAAM,IAAI,MAAM,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,SAAc,CAAC;QAC9C,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;IACjC,CAAC;IAED,IAAI,CAAC,KAAsB;QACzB,OAAO,IAAI,CAAC,GAAG,CAAS,MAAM,EAAE,cAAc,EAAE,KAAK,CAAC,CAAC;IACzD,CAAC;IAED,QAAQ,CAAC,MAAyB;QAChC,OAAO,IAAI,CAAC,GAAG,CACb,MAAM,EAAE,mBAAmB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAC/C,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAkB;QACvB,OAAO,IAAI,CAAC,GAAG,CAAW,MAAM,EAAE,qBAAqB,EAAE,KAAK,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,CAAC,KAAgB;QACnB,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,IAAI,KAAK,CAAC,SAAS;YAAE,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9D,IAAI,KAAK,CAAC,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1D,IAAI,KAAK,CAAC,MAAM;YAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7D,OAAO,IAAI,CAAC,GAAG,CAAW,KAAK,EAAE,gBAAgB,MAAM,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,GAAG,CAAgB,KAAK,EAAE,gBAAgB,EAAE,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,IAAI,CAAC,GAAG,CAAO,QAAQ,EAAE,gBAAgB,EAAE,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,UAAU;QACR,OAAO,IAAI,CAAC,GAAG,CAAkB,KAAK,EAAE,gBAAgB,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,KAAK,KAA0B,CAAC;CACvC"}
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/store/remote.ts"],"names":[],"mappings":"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"}