@lumetra/engram 0.1.0 → 0.3.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 CHANGED
@@ -37,6 +37,10 @@ console.log(result.answer);
37
37
  console.log(result.explanation?.retrieved_memories);
38
38
  ```
39
39
 
40
+ ### Automatic 429 retry
41
+
42
+ The Engram API enforces a per-tenant concurrent-request cap and returns `429 Too Many Requests` with a `Retry-After` header when you exceed it. The client honors that header automatically (up to `maxRetriesOn429` attempts, default 3, capped at 30s per sleep) so bursty workloads don't fail on the first contention spike. Pass `maxRetriesOn429: 0` in the constructor to opt out and surface 429 as `EngramError` immediately.
43
+
40
44
  ## Configuration
41
45
 
42
46
  ```ts
@@ -53,26 +57,57 @@ new EngramClient({
53
57
  ## API surface
54
58
 
55
59
  ### Memories
56
- - `storeMemory(content, bucket?)` — store a single fact
57
- - `storeMemories(contents[], bucket?)` — batched store
58
- - `listMemories(bucket?, { limit?, offset? })` — paginated list
59
- - `deleteMemory(memoryId, bucket?)` — delete one memory
60
- - `clearMemories(bucket)` — delete every memory in a bucket
60
+ - `storeMemory(content, bucket?)` — store a single fact. `bucket` defaults to `"default"`.
61
+ - `storeMemories(contents[], bucket?)` — batched store. `bucket` defaults to `"default"`.
62
+ - `listMemories(bucket?, { limit?, offset? })` — paginated list (`limit` defaults to 20, `offset` to 0).
63
+ - `deleteMemory(memoryId, bucket?)` — delete one memory. `bucket` defaults to `"default"`.
64
+ - `clearMemories(bucket)` — delete every memory in a bucket. **No default — explicit bucket required** (prevents accidental wipes).
61
65
 
62
66
  ### Query
63
67
  - `query(question, { buckets?, topK?, skipSynthesis?, returnExplanation? })`
64
- - `buckets` fuses across multiple buckets in one call
65
- - `skipSynthesis: true` returns retrieval-only — no server-side LLM call
68
+ - `buckets` fuses across multiple buckets in one call. Defaults to `["default"]`.
69
+ - `topK` defaults to `8`.
70
+ - `skipSynthesis: true` returns retrieval-only — no server-side LLM call. Defaults to `false`.
71
+ - `returnExplanation` defaults to `true`.
66
72
  - response shape: `{ answer, explanation: { retrieved_memories, profile, graph_facts }, usage }`
73
+ - `queryStream(question, options?)` — same args, returns an `AsyncIterable<QueryStreamEvent>` that streams the answer
74
+
75
+ ## Streaming
76
+
77
+ For broad questions, synthesis can take 10–25 seconds. `queryStream` yields the answer incrementally so you can render it as it's produced instead of waiting for the full response:
78
+
79
+ ```ts
80
+ for await (const event of engram.queryStream('Summarize what I worked on this week', { buckets: ['work'] })) {
81
+ if (event.type === 'delta') {
82
+ process.stdout.write(event.content);
83
+ } else if (event.type === 'done') {
84
+ console.log();
85
+ console.log(`Used ${event.usage?.output_tokens} tokens`);
86
+ }
87
+ }
88
+ ```
89
+
90
+ Two frame types (discriminated by `type`):
91
+
92
+ ```ts
93
+ type QueryStreamEvent =
94
+ | { type: 'delta'; content: string }
95
+ | { type: 'done'; usage?: QueryUsage; synthesis_usage?: unknown; explanation?: QueryExplanation };
96
+ ```
97
+
98
+ - `delta` frames carry incremental synthesis output, in order. Zero or more.
99
+ - `done` is emitted exactly once at the end with final usage and explanation.
100
+
101
+ Break out of the `for await` loop to abort the request — the iterator's `return()` cancels the upstream fetch.
67
102
 
68
103
  ### Buckets
69
104
  - `listBuckets()` — all buckets in your tenant
70
105
  - `createBucket(name, description?)`
71
- - `deleteBucket(bucket)`
106
+ - `deleteBucket(bucket)` — **No default — explicit bucket required** (prevents accidental wipes).
72
107
 
73
108
  ### Profile
74
- - `getProfile(bucket?)` — the canonical profile prepended to recall
75
- - `regenerateProfile(bucket?)` — rebuild from current memories
109
+ - `getProfile(bucket?)` — the canonical profile prepended to recall. `bucket` defaults to `"default"`.
110
+ - `regenerateProfile(bucket?)` — rebuild from current memories. `bucket` defaults to `"default"`.
76
111
 
77
112
  ### Errors
78
113
 
package/dist/index.cjs CHANGED
@@ -15,11 +15,28 @@ var EngramError = class extends Error {
15
15
  // src/client.ts
16
16
  var DEFAULT_BASE_URL = "https://api.lumetra.io";
17
17
  var DEFAULT_TIMEOUT_MS = 3e4;
18
+ var DEFAULT_MAX_RETRIES_ON_429 = 3;
19
+ var RETRY_AFTER_CAP_MS = 3e4;
20
+ var SDK_VERSION = "0.3.0";
21
+ var USER_AGENT = `engram-js/${SDK_VERSION}`;
22
+ function parseRetryAfterMs(header, defaultBackoffMs) {
23
+ if (header) {
24
+ const value = Number(header.trim());
25
+ if (Number.isFinite(value) && value >= 0) {
26
+ return Math.min(value * 1e3, RETRY_AFTER_CAP_MS);
27
+ }
28
+ }
29
+ return Math.min(defaultBackoffMs, RETRY_AFTER_CAP_MS);
30
+ }
31
+ function sleep(ms) {
32
+ return new Promise((resolve) => setTimeout(resolve, ms));
33
+ }
18
34
  var EngramClient = class {
19
35
  apiKey;
20
36
  baseUrl;
21
37
  fetchImpl;
22
38
  timeoutMs;
39
+ maxRetriesOn429;
23
40
  constructor(options = {}) {
24
41
  const apiKey = options.apiKey ?? (typeof process !== "undefined" ? process.env?.ENGRAM_API_KEY : void 0) ?? "";
25
42
  if (!apiKey) {
@@ -32,6 +49,7 @@ var EngramClient = class {
32
49
  this.baseUrl = baseUrl.replace(/\/+$/, "");
33
50
  this.fetchImpl = options.fetch ?? globalThis.fetch;
34
51
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
52
+ this.maxRetriesOn429 = Math.max(0, options.maxRetriesOn429 ?? DEFAULT_MAX_RETRIES_ON_429);
35
53
  if (typeof this.fetchImpl !== "function") {
36
54
  throw new Error(
37
55
  "EngramClient: no fetch implementation available. Use Node 18+, or pass options.fetch."
@@ -47,40 +65,54 @@ var EngramClient = class {
47
65
  if (v !== void 0) url.searchParams.set(k, String(v));
48
66
  }
49
67
  }
50
- const controller = new AbortController();
51
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
52
- let res;
53
- try {
54
- res = await this.fetchImpl(url.toString(), {
55
- method: init.method,
56
- headers: {
57
- Authorization: `Bearer ${this.apiKey}`,
58
- "Content-Type": "application/json"
59
- },
60
- body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
61
- signal: controller.signal
62
- });
63
- } finally {
64
- clearTimeout(timer);
65
- }
66
- const text = await res.text();
67
- let parsed = void 0;
68
- if (text) {
68
+ const requestBody = init.body !== void 0 ? JSON.stringify(init.body) : void 0;
69
+ let attemptsRemaining = this.maxRetriesOn429;
70
+ let backoffMs = 1e3;
71
+ while (true) {
72
+ const controller = new AbortController();
73
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
74
+ let res;
69
75
  try {
70
- parsed = JSON.parse(text);
71
- } catch {
72
- parsed = text;
76
+ res = await this.fetchImpl(url.toString(), {
77
+ method: init.method,
78
+ headers: {
79
+ Authorization: `Bearer ${this.apiKey}`,
80
+ "Content-Type": "application/json",
81
+ "User-Agent": USER_AGENT
82
+ },
83
+ body: requestBody,
84
+ signal: controller.signal
85
+ });
86
+ } finally {
87
+ clearTimeout(timer);
73
88
  }
89
+ if (res.status === 429 && attemptsRemaining > 0) {
90
+ await res.text().catch(() => void 0);
91
+ const delay = parseRetryAfterMs(res.headers.get("Retry-After"), backoffMs);
92
+ await sleep(delay);
93
+ attemptsRemaining -= 1;
94
+ backoffMs = Math.min(backoffMs * 2, RETRY_AFTER_CAP_MS);
95
+ continue;
96
+ }
97
+ const text = await res.text();
98
+ let parsed = void 0;
99
+ if (text) {
100
+ try {
101
+ parsed = JSON.parse(text);
102
+ } catch {
103
+ parsed = text;
104
+ }
105
+ }
106
+ if (!res.ok) {
107
+ const detail = parsed && typeof parsed === "object" && parsed !== null && "error" in parsed ? parsed.error : parsed;
108
+ throw new EngramError(
109
+ `Engram API ${res.status}: ${typeof detail === "string" ? detail : JSON.stringify(detail ?? "")}`,
110
+ res.status,
111
+ parsed
112
+ );
113
+ }
114
+ return parsed;
74
115
  }
75
- if (!res.ok) {
76
- const detail = parsed && typeof parsed === "object" && parsed !== null && "error" in parsed ? parsed.error : parsed;
77
- throw new EngramError(
78
- `Engram API ${res.status}: ${typeof detail === "string" ? detail : JSON.stringify(detail ?? "")}`,
79
- res.status,
80
- parsed
81
- );
82
- }
83
- return parsed;
84
116
  }
85
117
  // ---------- Memories ----------
86
118
  async storeMemory(content, bucket = "default") {
@@ -90,10 +122,11 @@ var EngramClient = class {
90
122
  );
91
123
  }
92
124
  async storeMemories(contents, bucket = "default") {
93
- return this.request(
125
+ const result = await this.request(
94
126
  `/v1/buckets/${encodeURIComponent(bucket)}/memories`,
95
127
  { method: "POST", body: { memories: contents.map((content) => ({ content })) } }
96
128
  );
129
+ return Array.isArray(result) ? { memories: result } : result;
97
130
  }
98
131
  async listMemories(bucket = "default", options = {}) {
99
132
  return this.request(
@@ -111,10 +144,11 @@ var EngramClient = class {
111
144
  );
112
145
  }
113
146
  async clearMemories(bucket) {
114
- await this.request(
147
+ const res = await this.request(
115
148
  `/v1/buckets/${encodeURIComponent(bucket)}/memories`,
116
149
  { method: "DELETE" }
117
150
  );
151
+ return res ?? { success: true, cleared_count: 0 };
118
152
  }
119
153
  // ---------- Query ----------
120
154
  async query(question, options = {}) {
@@ -132,6 +166,183 @@ var EngramClient = class {
132
166
  }
133
167
  });
134
168
  }
169
+ /**
170
+ * Streaming variant of {@link query}. Returns an async-iterable that
171
+ * yields {@link QueryStreamEvent} frames as the server produces them:
172
+ *
173
+ * for await (const ev of engram.queryStream('...')) {
174
+ * if (ev.type === 'delta') process.stdout.write(ev.content);
175
+ * else if (ev.type === 'done') console.log(ev.usage);
176
+ * }
177
+ *
178
+ * Break out of the loop to abort the request (the underlying
179
+ * AbortController is wired up to the fetch call).
180
+ */
181
+ queryStream(question, options = {}) {
182
+ const buckets = options.buckets ?? ["default"];
183
+ const body = {
184
+ query: question,
185
+ buckets,
186
+ stream: true,
187
+ options: {
188
+ top_k: options.topK ?? 8,
189
+ return_explanation: options.returnExplanation ?? true,
190
+ skip_synthesis: options.skipSynthesis ?? false
191
+ }
192
+ };
193
+ const url = `${this.baseUrl}/v1/query`;
194
+ const apiKey = this.apiKey;
195
+ const fetchImpl = this.fetchImpl;
196
+ const timeoutMs = this.timeoutMs;
197
+ const maxRetriesOn429 = this.maxRetriesOn429;
198
+ const bodyJson = JSON.stringify(body);
199
+ return {
200
+ [Symbol.asyncIterator]: () => {
201
+ const controller = new AbortController();
202
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
203
+ let started = false;
204
+ let reader = null;
205
+ const decoder = new TextDecoder("utf-8");
206
+ let buffer = "";
207
+ let done = false;
208
+ const queue = [];
209
+ let pendingError = null;
210
+ const ensureStarted = async () => {
211
+ if (started) return;
212
+ started = true;
213
+ let attemptsRemaining = maxRetriesOn429;
214
+ let backoffMs = 1e3;
215
+ while (true) {
216
+ const res = await fetchImpl(url, {
217
+ method: "POST",
218
+ headers: {
219
+ Authorization: `Bearer ${apiKey}`,
220
+ "Content-Type": "application/json",
221
+ Accept: "text/event-stream",
222
+ "User-Agent": USER_AGENT
223
+ },
224
+ body: bodyJson,
225
+ signal: controller.signal
226
+ });
227
+ if (res.status === 429 && attemptsRemaining > 0) {
228
+ await res.text().catch(() => void 0);
229
+ const delay = parseRetryAfterMs(res.headers.get("Retry-After"), backoffMs);
230
+ await sleep(delay);
231
+ attemptsRemaining -= 1;
232
+ backoffMs = Math.min(backoffMs * 2, RETRY_AFTER_CAP_MS);
233
+ continue;
234
+ }
235
+ if (!res.ok) {
236
+ const text = await res.text().catch(() => "");
237
+ let parsed = text;
238
+ try {
239
+ parsed = text ? JSON.parse(text) : text;
240
+ } catch {
241
+ }
242
+ const detail = parsed && typeof parsed === "object" && parsed !== null && "error" in parsed ? parsed.error : parsed;
243
+ throw new EngramError(
244
+ `Engram API ${res.status}: ${typeof detail === "string" ? detail : JSON.stringify(detail ?? "")}`,
245
+ res.status,
246
+ parsed
247
+ );
248
+ }
249
+ if (!res.body) {
250
+ throw new EngramError("Engram API: streaming response has no body", res.status, null);
251
+ }
252
+ reader = res.body.getReader();
253
+ return;
254
+ }
255
+ };
256
+ const drainBuffer = () => {
257
+ let idx;
258
+ while ((idx = buffer.indexOf("\n\n")) !== -1) {
259
+ const frame = buffer.slice(0, idx);
260
+ buffer = buffer.slice(idx + 2);
261
+ const dataLines = [];
262
+ for (const rawLine of frame.split("\n")) {
263
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
264
+ if (line.startsWith("data: ")) {
265
+ dataLines.push(line.slice(6));
266
+ } else if (line.startsWith("data:")) {
267
+ dataLines.push(line.slice(5));
268
+ }
269
+ }
270
+ if (dataLines.length === 0) continue;
271
+ const payloadStr = dataLines.join("\n");
272
+ if (payloadStr === "[DONE]") {
273
+ done = true;
274
+ return;
275
+ }
276
+ let payload;
277
+ try {
278
+ payload = JSON.parse(payloadStr);
279
+ } catch {
280
+ continue;
281
+ }
282
+ if (payload && typeof payload === "object") {
283
+ const obj = payload;
284
+ if (obj.error) {
285
+ pendingError = new EngramError(String(obj.error), 0, obj);
286
+ done = true;
287
+ return;
288
+ }
289
+ const choices = obj.choices;
290
+ if (Array.isArray(choices) && choices.length > 0) {
291
+ const delta = choices[0]?.delta?.content;
292
+ if (typeof delta === "string" && delta.length > 0) {
293
+ queue.push({ type: "delta", content: delta });
294
+ }
295
+ continue;
296
+ }
297
+ queue.push({ type: "done", ...obj });
298
+ }
299
+ }
300
+ };
301
+ return {
302
+ next: async () => {
303
+ try {
304
+ await ensureStarted();
305
+ } catch (err) {
306
+ clearTimeout(timer);
307
+ throw err;
308
+ }
309
+ while (queue.length === 0 && !done) {
310
+ if (!reader) {
311
+ clearTimeout(timer);
312
+ throw new EngramError("Engram API: stream reader missing", 0, null);
313
+ }
314
+ const chunk = await reader.read();
315
+ if (chunk.done) {
316
+ if (buffer.length > 0) {
317
+ buffer += "\n\n";
318
+ drainBuffer();
319
+ }
320
+ done = true;
321
+ break;
322
+ }
323
+ buffer += decoder.decode(chunk.value, { stream: true });
324
+ drainBuffer();
325
+ }
326
+ if (queue.length > 0) {
327
+ return { value: queue.shift(), done: false };
328
+ }
329
+ clearTimeout(timer);
330
+ if (pendingError) throw pendingError;
331
+ return { value: void 0, done: true };
332
+ },
333
+ return: async () => {
334
+ clearTimeout(timer);
335
+ controller.abort();
336
+ try {
337
+ await reader?.cancel();
338
+ } catch {
339
+ }
340
+ return { value: void 0, done: true };
341
+ }
342
+ };
343
+ }
344
+ };
345
+ }
135
346
  // ---------- Buckets ----------
136
347
  async listBuckets() {
137
348
  const result = await this.request(`/v1/buckets`, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export interface EngramClientOptions {\n /**\n * Engram API key. Looks like `eng_live_...`. Defaults to `process.env.ENGRAM_API_KEY`.\n */\n apiKey?: string;\n /**\n * API base URL. Defaults to `process.env.ENGRAM_BASE_URL` or `https://api.lumetra.io`.\n */\n baseUrl?: string;\n /**\n * Custom fetch implementation. Defaults to the global `fetch`.\n * Useful for proxying, retry middleware, or non-Node runtimes.\n */\n fetch?: typeof fetch;\n /**\n * Request timeout in milliseconds. Defaults to 30000 (30s).\n */\n timeoutMs?: number;\n}\n\nexport interface Bucket {\n id: string;\n name: string;\n description?: string | null;\n created_at: string;\n memory_count?: number;\n}\n\nexport interface Memory {\n id: string;\n content: string;\n bucket_name?: string;\n created_at?: string;\n token_count?: number;\n}\n\nexport interface StoreMemoryResult {\n id: string;\n bucket_name: string;\n token_count: number;\n}\n\nexport interface RetrievedMemory {\n id?: string;\n content: string;\n score?: number;\n bucket?: string;\n}\n\nexport interface QueryExplanation {\n retrieved_memories?: RetrievedMemory[];\n profile?: string | null;\n graph_facts?: string[];\n}\n\nexport interface QueryUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}\n\nexport interface QueryResult {\n answer: string;\n explanation?: QueryExplanation;\n usage?: QueryUsage;\n}\n\nexport interface QueryOptions {\n /**\n * Buckets to fuse across. Defaults to `['default']`.\n */\n buckets?: string[];\n /**\n * Maximum number of memories to retrieve. Defaults to 8.\n */\n topK?: number;\n /**\n * If true, server skips the synthesis LLM call and returns retrieval-only.\n * `answer` will be an empty string in that case. Defaults to false.\n */\n skipSynthesis?: boolean;\n /**\n * Whether to populate the `explanation` field. Defaults to true.\n */\n returnExplanation?: boolean;\n}\n\nexport interface ListMemoriesOptions {\n limit?: number;\n offset?: number;\n}\n\nexport interface ListMemoriesResult {\n memories: Memory[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport class EngramError extends Error {\n override readonly name = 'EngramError';\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.status = status;\n this.body = body;\n }\n}\n","import {\n EngramError,\n type Bucket,\n type EngramClientOptions,\n type ListMemoriesOptions,\n type ListMemoriesResult,\n type QueryOptions,\n type QueryResult,\n type StoreMemoryResult,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.lumetra.io';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport class EngramClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n\n constructor(options: EngramClientOptions = {}) {\n const apiKey =\n options.apiKey ??\n (typeof process !== 'undefined' ? process.env?.ENGRAM_API_KEY : undefined) ??\n '';\n if (!apiKey) {\n throw new Error(\n 'EngramClient: apiKey is required. Pass it explicitly or set ENGRAM_API_KEY in your environment.',\n );\n }\n const baseUrl =\n options.baseUrl ??\n (typeof process !== 'undefined' ? process.env?.ENGRAM_BASE_URL : undefined) ??\n DEFAULT_BASE_URL;\n\n this.apiKey = apiKey;\n this.baseUrl = baseUrl.replace(/\\/+$/, '');\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (typeof this.fetchImpl !== 'function') {\n throw new Error(\n 'EngramClient: no fetch implementation available. Use Node 18+, or pass options.fetch.',\n );\n }\n }\n\n private async request<T>(\n path: string,\n init: { method: string; body?: unknown; query?: Record<string, string | number | undefined> } = {\n method: 'GET',\n },\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (init.query) {\n for (const [k, v] of Object.entries(init.query)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let res: Response;\n try {\n res = await this.fetchImpl(url.toString(), {\n method: init.method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n\n const text = await res.text();\n let parsed: unknown = undefined;\n if (text) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n const detail =\n parsed && typeof parsed === 'object' && parsed !== null && 'error' in parsed\n ? (parsed as { error: unknown }).error\n : parsed;\n throw new EngramError(\n `Engram API ${res.status}: ${typeof detail === 'string' ? detail : JSON.stringify(detail ?? '')}`,\n res.status,\n parsed,\n );\n }\n\n return parsed as T;\n }\n\n // ---------- Memories ----------\n\n async storeMemory(content: string, bucket: string = 'default'): Promise<StoreMemoryResult> {\n return this.request<StoreMemoryResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'POST', body: { content } },\n );\n }\n\n async storeMemories(\n contents: string[],\n bucket: string = 'default',\n ): Promise<{ memories: StoreMemoryResult[] }> {\n return this.request<{ memories: StoreMemoryResult[] }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'POST', body: { memories: contents.map((content) => ({ content })) } },\n );\n }\n\n async listMemories(\n bucket: string = 'default',\n options: ListMemoriesOptions = {},\n ): Promise<ListMemoriesResult> {\n return this.request<ListMemoriesResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n {\n method: 'GET',\n query: { limit: options.limit ?? 20, offset: options.offset ?? 0 },\n },\n );\n }\n\n async deleteMemory(memoryId: string, bucket: string = 'default'): Promise<void> {\n await this.request<unknown>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories/${encodeURIComponent(memoryId)}`,\n { method: 'DELETE' },\n );\n }\n\n async clearMemories(bucket: string): Promise<void> {\n await this.request<unknown>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'DELETE' },\n );\n }\n\n // ---------- Query ----------\n\n async query(question: string, options: QueryOptions = {}): Promise<QueryResult> {\n const buckets = options.buckets ?? ['default'];\n return this.request<QueryResult>('/v1/query', {\n method: 'POST',\n body: {\n query: question,\n buckets,\n options: {\n top_k: options.topK ?? 8,\n return_explanation: options.returnExplanation ?? true,\n skip_synthesis: options.skipSynthesis ?? false,\n },\n },\n });\n }\n\n // ---------- Buckets ----------\n\n async listBuckets(): Promise<Bucket[]> {\n const result = await this.request<{ buckets: Bucket[] } | Bucket[]>(`/v1/buckets`, {\n method: 'GET',\n });\n return Array.isArray(result) ? result : result.buckets;\n }\n\n async createBucket(name: string, description?: string): Promise<Bucket> {\n return this.request<Bucket>('/v1/buckets', {\n method: 'POST',\n body: { name, description },\n });\n }\n\n async deleteBucket(bucket: string): Promise<void> {\n await this.request<unknown>(`/v1/buckets/${encodeURIComponent(bucket)}`, {\n method: 'DELETE',\n });\n }\n\n // ---------- Profile ----------\n\n async getProfile(bucket: string = 'default'): Promise<{ profile: string | null }> {\n return this.request<{ profile: string | null }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/profile`,\n { method: 'GET' },\n );\n }\n\n async regenerateProfile(bucket: string = 'default'): Promise<{ profile: string | null }> {\n return this.request<{ profile: string | null }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/profile/regenerate`,\n { method: 'POST' },\n );\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export interface EngramClientOptions {\n /**\n * Engram API key. Looks like `eng_live_...`. Defaults to `process.env.ENGRAM_API_KEY`.\n */\n apiKey?: string;\n /**\n * API base URL. Defaults to `process.env.ENGRAM_BASE_URL` or `https://api.lumetra.io`.\n */\n baseUrl?: string;\n /**\n * Custom fetch implementation. Defaults to the global `fetch`.\n * Useful for proxying, retry middleware, or non-Node runtimes.\n */\n fetch?: typeof fetch;\n /**\n * Request timeout in milliseconds. Defaults to 30000 (30s).\n */\n timeoutMs?: number;\n /**\n * How many times to retry on a 429 (per-tenant concurrent-request cap).\n * Honors the server's `Retry-After` header, capped at 30s per sleep.\n * Defaults to 3. Set to 0 to disable retry and surface 429 as `EngramError`\n * on the first attempt.\n */\n maxRetriesOn429?: number;\n}\n\nexport interface Bucket {\n id: string;\n name: string;\n description?: string | null;\n created_at: string;\n memory_count?: number;\n}\n\nexport interface Memory {\n id: string;\n content: string;\n bucket_name?: string;\n created_at?: string;\n token_count?: number;\n}\n\nexport interface StoreMemoryResult {\n id: string;\n bucket_name: string;\n token_count: number;\n}\n\nexport interface ClearMemoriesResult {\n success: boolean;\n /** Number of memories actually deleted (server-reported). */\n cleared_count: number;\n}\n\nexport interface RetrievedMemory {\n id?: string;\n content: string;\n score?: number;\n bucket?: string;\n}\n\nexport interface QueryExplanation {\n retrieved_memories?: RetrievedMemory[];\n profile?: string | null;\n graph_facts?: string[];\n}\n\nexport interface QueryUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}\n\nexport interface QueryResult {\n answer: string;\n explanation?: QueryExplanation;\n usage?: QueryUsage;\n}\n\n/**\n * One frame yielded by {@link EngramClient.queryStream}.\n *\n * The shape is discriminated by `type`:\n * - `delta` frames carry an incremental piece of the answer in `content`.\n * - `done` carries the final usage + (optional) explanation. Emitted\n * exactly once at the end of the stream.\n */\nexport type QueryStreamEvent =\n | { type: 'delta'; content: string }\n | {\n type: 'done';\n usage?: QueryUsage;\n synthesis_usage?: unknown;\n explanation?: QueryExplanation;\n };\n\nexport interface QueryOptions {\n /**\n * Buckets to fuse across. Defaults to `['default']`.\n */\n buckets?: string[];\n /**\n * Maximum number of memories to retrieve. Defaults to 8.\n */\n topK?: number;\n /**\n * If true, server skips the synthesis LLM call and returns retrieval-only.\n * `answer` will be an empty string in that case. Defaults to false.\n */\n skipSynthesis?: boolean;\n /**\n * Whether to populate the `explanation` field. Defaults to true.\n */\n returnExplanation?: boolean;\n}\n\nexport interface ListMemoriesOptions {\n limit?: number;\n offset?: number;\n}\n\nexport interface ListMemoriesResult {\n memories: Memory[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport class EngramError extends Error {\n override readonly name = 'EngramError';\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.status = status;\n this.body = body;\n }\n}\n","import {\n EngramError,\n type Bucket,\n type ClearMemoriesResult,\n type EngramClientOptions,\n type ListMemoriesOptions,\n type ListMemoriesResult,\n type QueryOptions,\n type QueryResult,\n type QueryStreamEvent,\n type StoreMemoryResult,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.lumetra.io';\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_RETRIES_ON_429 = 3;\n// Cap on per-attempt backoff so a misconfigured server can't force\n// callers to sleep for minutes.\nconst RETRY_AFTER_CAP_MS = 30_000;\nconst SDK_VERSION = '0.3.0';\nconst USER_AGENT = `engram-js/${SDK_VERSION}`;\n\nfunction parseRetryAfterMs(header: string | null, defaultBackoffMs: number): number {\n if (header) {\n const value = Number(header.trim());\n if (Number.isFinite(value) && value >= 0) {\n return Math.min(value * 1000, RETRY_AFTER_CAP_MS);\n }\n }\n return Math.min(defaultBackoffMs, RETRY_AFTER_CAP_MS);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport class EngramClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly maxRetriesOn429: number;\n\n constructor(options: EngramClientOptions = {}) {\n const apiKey =\n options.apiKey ??\n (typeof process !== 'undefined' ? process.env?.ENGRAM_API_KEY : undefined) ??\n '';\n if (!apiKey) {\n throw new Error(\n 'EngramClient: apiKey is required. Pass it explicitly or set ENGRAM_API_KEY in your environment.',\n );\n }\n const baseUrl =\n options.baseUrl ??\n (typeof process !== 'undefined' ? process.env?.ENGRAM_BASE_URL : undefined) ??\n DEFAULT_BASE_URL;\n\n this.apiKey = apiKey;\n this.baseUrl = baseUrl.replace(/\\/+$/, '');\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.maxRetriesOn429 = Math.max(0, options.maxRetriesOn429 ?? DEFAULT_MAX_RETRIES_ON_429);\n\n if (typeof this.fetchImpl !== 'function') {\n throw new Error(\n 'EngramClient: no fetch implementation available. Use Node 18+, or pass options.fetch.',\n );\n }\n }\n\n private async request<T>(\n path: string,\n init: { method: string; body?: unknown; query?: Record<string, string | number | undefined> } = {\n method: 'GET',\n },\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (init.query) {\n for (const [k, v] of Object.entries(init.query)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n\n // 429-aware retry. The Engram API enforces a per-tenant concurrent-\n // request cap and sets Retry-After on 429s; without retry handling,\n // bursty clients fail immediately under load. The body is JSON-\n // serialized once outside the loop so each attempt sends an\n // identical request.\n const requestBody = init.body !== undefined ? JSON.stringify(init.body) : undefined;\n let attemptsRemaining = this.maxRetriesOn429;\n let backoffMs = 1000;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let res: Response;\n try {\n res = await this.fetchImpl(url.toString(), {\n method: init.method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n 'User-Agent': USER_AGENT,\n },\n body: requestBody,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n\n if (res.status === 429 && attemptsRemaining > 0) {\n // Drain the body so the connection can return to the pool.\n await res.text().catch(() => undefined);\n const delay = parseRetryAfterMs(res.headers.get('Retry-After'), backoffMs);\n await sleep(delay);\n attemptsRemaining -= 1;\n backoffMs = Math.min(backoffMs * 2, RETRY_AFTER_CAP_MS);\n continue;\n }\n\n const text = await res.text();\n let parsed: unknown = undefined;\n if (text) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n const detail =\n parsed && typeof parsed === 'object' && parsed !== null && 'error' in parsed\n ? (parsed as { error: unknown }).error\n : parsed;\n throw new EngramError(\n `Engram API ${res.status}: ${typeof detail === 'string' ? detail : JSON.stringify(detail ?? '')}`,\n res.status,\n parsed,\n );\n }\n\n return parsed as T;\n }\n }\n\n // ---------- Memories ----------\n\n async storeMemory(content: string, bucket: string = 'default'): Promise<StoreMemoryResult> {\n return this.request<StoreMemoryResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'POST', body: { content } },\n );\n }\n\n async storeMemories(\n contents: string[],\n bucket: string = 'default',\n ): Promise<{ memories: StoreMemoryResult[] }> {\n // Defensively unwrap: depending on server version the batch endpoint\n // returns either { memories: [...] } or a bare array. Normalize to the\n // wrapped shape so callers don't have to switch on it.\n const result = await this.request<{ memories: StoreMemoryResult[] } | StoreMemoryResult[]>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'POST', body: { memories: contents.map((content) => ({ content })) } },\n );\n return Array.isArray(result) ? { memories: result } : result;\n }\n\n async listMemories(\n bucket: string = 'default',\n options: ListMemoriesOptions = {},\n ): Promise<ListMemoriesResult> {\n return this.request<ListMemoriesResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n {\n method: 'GET',\n query: { limit: options.limit ?? 20, offset: options.offset ?? 0 },\n },\n );\n }\n\n async deleteMemory(memoryId: string, bucket: string = 'default'): Promise<void> {\n await this.request<unknown>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories/${encodeURIComponent(memoryId)}`,\n { method: 'DELETE' },\n );\n }\n\n async clearMemories(bucket: string): Promise<ClearMemoriesResult> {\n const res = await this.request<ClearMemoriesResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'DELETE' },\n );\n // Defensive default: older servers / proxies may strip the body. The\n // contract surface is {success, cleared_count}; if the server stayed\n // silent we still return the same shape so callers can rely on it.\n return res ?? { success: true, cleared_count: 0 };\n }\n\n // ---------- Query ----------\n\n async query(question: string, options: QueryOptions = {}): Promise<QueryResult> {\n const buckets = options.buckets ?? ['default'];\n return this.request<QueryResult>('/v1/query', {\n method: 'POST',\n body: {\n query: question,\n buckets,\n options: {\n top_k: options.topK ?? 8,\n return_explanation: options.returnExplanation ?? true,\n skip_synthesis: options.skipSynthesis ?? false,\n },\n },\n });\n }\n\n /**\n * Streaming variant of {@link query}. Returns an async-iterable that\n * yields {@link QueryStreamEvent} frames as the server produces them:\n *\n * for await (const ev of engram.queryStream('...')) {\n * if (ev.type === 'delta') process.stdout.write(ev.content);\n * else if (ev.type === 'done') console.log(ev.usage);\n * }\n *\n * Break out of the loop to abort the request (the underlying\n * AbortController is wired up to the fetch call).\n */\n queryStream(question: string, options: QueryOptions = {}): AsyncIterable<QueryStreamEvent> {\n const buckets = options.buckets ?? ['default'];\n const body = {\n query: question,\n buckets,\n stream: true,\n options: {\n top_k: options.topK ?? 8,\n return_explanation: options.returnExplanation ?? true,\n skip_synthesis: options.skipSynthesis ?? false,\n },\n };\n const url = `${this.baseUrl}/v1/query`;\n const apiKey = this.apiKey;\n const fetchImpl = this.fetchImpl;\n const timeoutMs = this.timeoutMs;\n const maxRetriesOn429 = this.maxRetriesOn429;\n const bodyJson = JSON.stringify(body);\n\n return {\n [Symbol.asyncIterator]: () => {\n const controller = new AbortController();\n // The timeout caps total wall-clock for the stream. Set generously\n // because synthesis can run 10–25s before the first byte even with\n // streaming on slow paths; clamp to the user's configured timeout.\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let started = false;\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n const decoder = new TextDecoder('utf-8');\n let buffer = '';\n let done = false;\n const queue: QueryStreamEvent[] = [];\n let pendingError: unknown = null;\n\n const ensureStarted = async (): Promise<void> => {\n if (started) return;\n started = true;\n // 429-aware retry at the connection-open stage only. Once\n // the response body starts flowing we can't resume mid-\n // stream safely.\n let attemptsRemaining = maxRetriesOn429;\n let backoffMs = 1000;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const res = await fetchImpl(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n 'User-Agent': USER_AGENT,\n },\n body: bodyJson,\n signal: controller.signal,\n });\n if (res.status === 429 && attemptsRemaining > 0) {\n await res.text().catch(() => undefined);\n const delay = parseRetryAfterMs(res.headers.get('Retry-After'), backoffMs);\n await sleep(delay);\n attemptsRemaining -= 1;\n backoffMs = Math.min(backoffMs * 2, RETRY_AFTER_CAP_MS);\n continue;\n }\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n let parsed: unknown = text;\n try {\n parsed = text ? JSON.parse(text) : text;\n } catch {\n /* keep raw text */\n }\n const detail =\n parsed && typeof parsed === 'object' && parsed !== null && 'error' in parsed\n ? (parsed as { error: unknown }).error\n : parsed;\n throw new EngramError(\n `Engram API ${res.status}: ${typeof detail === 'string' ? detail : JSON.stringify(detail ?? '')}`,\n res.status,\n parsed,\n );\n }\n if (!res.body) {\n throw new EngramError('Engram API: streaming response has no body', res.status, null);\n }\n reader = res.body.getReader();\n return;\n }\n };\n\n const drainBuffer = (): void => {\n // SSE frames are separated by a blank line ('\\n\\n'). Each frame\n // is one or more 'field: value' lines. We only care about\n // 'data:' lines for this stream.\n let idx: number;\n while ((idx = buffer.indexOf('\\n\\n')) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n const dataLines: string[] = [];\n for (const rawLine of frame.split('\\n')) {\n const line = rawLine.endsWith('\\r') ? rawLine.slice(0, -1) : rawLine;\n if (line.startsWith('data: ')) {\n dataLines.push(line.slice(6));\n } else if (line.startsWith('data:')) {\n dataLines.push(line.slice(5));\n }\n }\n if (dataLines.length === 0) continue;\n const payloadStr = dataLines.join('\\n');\n if (payloadStr === '[DONE]') {\n done = true;\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(payloadStr);\n } catch {\n // Malformed frame — skip rather than corrupt the stream.\n continue;\n }\n if (payload && typeof payload === 'object') {\n const obj = payload as Record<string, unknown>;\n if (obj.error) {\n pendingError = new EngramError(String(obj.error), 0, obj);\n done = true;\n return;\n }\n // OpenAI-style delta chunk\n const choices = obj.choices as Array<{ delta?: { content?: string } }> | undefined;\n if (Array.isArray(choices) && choices.length > 0) {\n const delta = choices[0]?.delta?.content;\n if (typeof delta === 'string' && delta.length > 0) {\n queue.push({ type: 'delta', content: delta });\n }\n continue;\n }\n // Final usage / explanation frame (no 'choices' key).\n queue.push({ type: 'done', ...(obj as Omit<Extract<QueryStreamEvent, { type: 'done' }>, 'type'>) });\n }\n }\n };\n\n return {\n next: async (): Promise<IteratorResult<QueryStreamEvent>> => {\n try {\n await ensureStarted();\n } catch (err) {\n clearTimeout(timer);\n throw err;\n }\n while (queue.length === 0 && !done) {\n if (!reader) {\n clearTimeout(timer);\n throw new EngramError('Engram API: stream reader missing', 0, null);\n }\n const chunk = await reader.read();\n if (chunk.done) {\n // Flush whatever's left in the buffer (some servers don't\n // terminate with a trailing blank line).\n if (buffer.length > 0) {\n buffer += '\\n\\n';\n drainBuffer();\n }\n done = true;\n break;\n }\n buffer += decoder.decode(chunk.value, { stream: true });\n drainBuffer();\n }\n if (queue.length > 0) {\n return { value: queue.shift() as QueryStreamEvent, done: false };\n }\n clearTimeout(timer);\n if (pendingError) throw pendingError;\n return { value: undefined as unknown as QueryStreamEvent, done: true };\n },\n return: async (): Promise<IteratorResult<QueryStreamEvent>> => {\n // Caller broke out of the for-await loop — cancel the upstream\n // request so we're not holding the connection open.\n clearTimeout(timer);\n controller.abort();\n try {\n await reader?.cancel();\n } catch {\n /* ignored */\n }\n return { value: undefined as unknown as QueryStreamEvent, done: true };\n },\n };\n },\n };\n }\n\n // ---------- Buckets ----------\n\n async listBuckets(): Promise<Bucket[]> {\n const result = await this.request<{ buckets: Bucket[] } | Bucket[]>(`/v1/buckets`, {\n method: 'GET',\n });\n return Array.isArray(result) ? result : result.buckets;\n }\n\n async createBucket(name: string, description?: string): Promise<Bucket> {\n return this.request<Bucket>('/v1/buckets', {\n method: 'POST',\n body: { name, description },\n });\n }\n\n async deleteBucket(bucket: string): Promise<void> {\n await this.request<unknown>(`/v1/buckets/${encodeURIComponent(bucket)}`, {\n method: 'DELETE',\n });\n }\n\n // ---------- Profile ----------\n\n async getProfile(bucket: string = 'default'): Promise<{ profile: string | null }> {\n return this.request<{ profile: string | null }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/profile`,\n { method: 'GET' },\n );\n }\n\n async regenerateProfile(bucket: string = 'default'): Promise<{ profile: string | null }> {\n return this.request<{ profile: string | null }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/profile/regenerate`,\n { method: 'POST' },\n );\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -16,6 +16,13 @@ interface EngramClientOptions {
16
16
  * Request timeout in milliseconds. Defaults to 30000 (30s).
17
17
  */
18
18
  timeoutMs?: number;
19
+ /**
20
+ * How many times to retry on a 429 (per-tenant concurrent-request cap).
21
+ * Honors the server's `Retry-After` header, capped at 30s per sleep.
22
+ * Defaults to 3. Set to 0 to disable retry and surface 429 as `EngramError`
23
+ * on the first attempt.
24
+ */
25
+ maxRetriesOn429?: number;
19
26
  }
20
27
  interface Bucket {
21
28
  id: string;
@@ -36,6 +43,11 @@ interface StoreMemoryResult {
36
43
  bucket_name: string;
37
44
  token_count: number;
38
45
  }
46
+ interface ClearMemoriesResult {
47
+ success: boolean;
48
+ /** Number of memories actually deleted (server-reported). */
49
+ cleared_count: number;
50
+ }
39
51
  interface RetrievedMemory {
40
52
  id?: string;
41
53
  content: string;
@@ -57,6 +69,23 @@ interface QueryResult {
57
69
  explanation?: QueryExplanation;
58
70
  usage?: QueryUsage;
59
71
  }
72
+ /**
73
+ * One frame yielded by {@link EngramClient.queryStream}.
74
+ *
75
+ * The shape is discriminated by `type`:
76
+ * - `delta` frames carry an incremental piece of the answer in `content`.
77
+ * - `done` carries the final usage + (optional) explanation. Emitted
78
+ * exactly once at the end of the stream.
79
+ */
80
+ type QueryStreamEvent = {
81
+ type: 'delta';
82
+ content: string;
83
+ } | {
84
+ type: 'done';
85
+ usage?: QueryUsage;
86
+ synthesis_usage?: unknown;
87
+ explanation?: QueryExplanation;
88
+ };
60
89
  interface QueryOptions {
61
90
  /**
62
91
  * Buckets to fuse across. Defaults to `['default']`.
@@ -98,6 +127,7 @@ declare class EngramClient {
98
127
  private readonly baseUrl;
99
128
  private readonly fetchImpl;
100
129
  private readonly timeoutMs;
130
+ private readonly maxRetriesOn429;
101
131
  constructor(options?: EngramClientOptions);
102
132
  private request;
103
133
  storeMemory(content: string, bucket?: string): Promise<StoreMemoryResult>;
@@ -106,8 +136,21 @@ declare class EngramClient {
106
136
  }>;
107
137
  listMemories(bucket?: string, options?: ListMemoriesOptions): Promise<ListMemoriesResult>;
108
138
  deleteMemory(memoryId: string, bucket?: string): Promise<void>;
109
- clearMemories(bucket: string): Promise<void>;
139
+ clearMemories(bucket: string): Promise<ClearMemoriesResult>;
110
140
  query(question: string, options?: QueryOptions): Promise<QueryResult>;
141
+ /**
142
+ * Streaming variant of {@link query}. Returns an async-iterable that
143
+ * yields {@link QueryStreamEvent} frames as the server produces them:
144
+ *
145
+ * for await (const ev of engram.queryStream('...')) {
146
+ * if (ev.type === 'delta') process.stdout.write(ev.content);
147
+ * else if (ev.type === 'done') console.log(ev.usage);
148
+ * }
149
+ *
150
+ * Break out of the loop to abort the request (the underlying
151
+ * AbortController is wired up to the fetch call).
152
+ */
153
+ queryStream(question: string, options?: QueryOptions): AsyncIterable<QueryStreamEvent>;
111
154
  listBuckets(): Promise<Bucket[]>;
112
155
  createBucket(name: string, description?: string): Promise<Bucket>;
113
156
  deleteBucket(bucket: string): Promise<void>;
@@ -119,4 +162,4 @@ declare class EngramClient {
119
162
  }>;
120
163
  }
121
164
 
122
- export { type Bucket, EngramClient, type EngramClientOptions, EngramError, type ListMemoriesOptions, type ListMemoriesResult, type Memory, type QueryExplanation, type QueryOptions, type QueryResult, type QueryUsage, type RetrievedMemory, type StoreMemoryResult };
165
+ export { type Bucket, type ClearMemoriesResult, EngramClient, type EngramClientOptions, EngramError, type ListMemoriesOptions, type ListMemoriesResult, type Memory, type QueryExplanation, type QueryOptions, type QueryResult, type QueryStreamEvent, type QueryUsage, type RetrievedMemory, type StoreMemoryResult };
package/dist/index.d.ts CHANGED
@@ -16,6 +16,13 @@ interface EngramClientOptions {
16
16
  * Request timeout in milliseconds. Defaults to 30000 (30s).
17
17
  */
18
18
  timeoutMs?: number;
19
+ /**
20
+ * How many times to retry on a 429 (per-tenant concurrent-request cap).
21
+ * Honors the server's `Retry-After` header, capped at 30s per sleep.
22
+ * Defaults to 3. Set to 0 to disable retry and surface 429 as `EngramError`
23
+ * on the first attempt.
24
+ */
25
+ maxRetriesOn429?: number;
19
26
  }
20
27
  interface Bucket {
21
28
  id: string;
@@ -36,6 +43,11 @@ interface StoreMemoryResult {
36
43
  bucket_name: string;
37
44
  token_count: number;
38
45
  }
46
+ interface ClearMemoriesResult {
47
+ success: boolean;
48
+ /** Number of memories actually deleted (server-reported). */
49
+ cleared_count: number;
50
+ }
39
51
  interface RetrievedMemory {
40
52
  id?: string;
41
53
  content: string;
@@ -57,6 +69,23 @@ interface QueryResult {
57
69
  explanation?: QueryExplanation;
58
70
  usage?: QueryUsage;
59
71
  }
72
+ /**
73
+ * One frame yielded by {@link EngramClient.queryStream}.
74
+ *
75
+ * The shape is discriminated by `type`:
76
+ * - `delta` frames carry an incremental piece of the answer in `content`.
77
+ * - `done` carries the final usage + (optional) explanation. Emitted
78
+ * exactly once at the end of the stream.
79
+ */
80
+ type QueryStreamEvent = {
81
+ type: 'delta';
82
+ content: string;
83
+ } | {
84
+ type: 'done';
85
+ usage?: QueryUsage;
86
+ synthesis_usage?: unknown;
87
+ explanation?: QueryExplanation;
88
+ };
60
89
  interface QueryOptions {
61
90
  /**
62
91
  * Buckets to fuse across. Defaults to `['default']`.
@@ -98,6 +127,7 @@ declare class EngramClient {
98
127
  private readonly baseUrl;
99
128
  private readonly fetchImpl;
100
129
  private readonly timeoutMs;
130
+ private readonly maxRetriesOn429;
101
131
  constructor(options?: EngramClientOptions);
102
132
  private request;
103
133
  storeMemory(content: string, bucket?: string): Promise<StoreMemoryResult>;
@@ -106,8 +136,21 @@ declare class EngramClient {
106
136
  }>;
107
137
  listMemories(bucket?: string, options?: ListMemoriesOptions): Promise<ListMemoriesResult>;
108
138
  deleteMemory(memoryId: string, bucket?: string): Promise<void>;
109
- clearMemories(bucket: string): Promise<void>;
139
+ clearMemories(bucket: string): Promise<ClearMemoriesResult>;
110
140
  query(question: string, options?: QueryOptions): Promise<QueryResult>;
141
+ /**
142
+ * Streaming variant of {@link query}. Returns an async-iterable that
143
+ * yields {@link QueryStreamEvent} frames as the server produces them:
144
+ *
145
+ * for await (const ev of engram.queryStream('...')) {
146
+ * if (ev.type === 'delta') process.stdout.write(ev.content);
147
+ * else if (ev.type === 'done') console.log(ev.usage);
148
+ * }
149
+ *
150
+ * Break out of the loop to abort the request (the underlying
151
+ * AbortController is wired up to the fetch call).
152
+ */
153
+ queryStream(question: string, options?: QueryOptions): AsyncIterable<QueryStreamEvent>;
111
154
  listBuckets(): Promise<Bucket[]>;
112
155
  createBucket(name: string, description?: string): Promise<Bucket>;
113
156
  deleteBucket(bucket: string): Promise<void>;
@@ -119,4 +162,4 @@ declare class EngramClient {
119
162
  }>;
120
163
  }
121
164
 
122
- export { type Bucket, EngramClient, type EngramClientOptions, EngramError, type ListMemoriesOptions, type ListMemoriesResult, type Memory, type QueryExplanation, type QueryOptions, type QueryResult, type QueryUsage, type RetrievedMemory, type StoreMemoryResult };
165
+ export { type Bucket, type ClearMemoriesResult, EngramClient, type EngramClientOptions, EngramError, type ListMemoriesOptions, type ListMemoriesResult, type Memory, type QueryExplanation, type QueryOptions, type QueryResult, type QueryStreamEvent, type QueryUsage, type RetrievedMemory, type StoreMemoryResult };
package/dist/index.js CHANGED
@@ -13,11 +13,28 @@ var EngramError = class extends Error {
13
13
  // src/client.ts
14
14
  var DEFAULT_BASE_URL = "https://api.lumetra.io";
15
15
  var DEFAULT_TIMEOUT_MS = 3e4;
16
+ var DEFAULT_MAX_RETRIES_ON_429 = 3;
17
+ var RETRY_AFTER_CAP_MS = 3e4;
18
+ var SDK_VERSION = "0.3.0";
19
+ var USER_AGENT = `engram-js/${SDK_VERSION}`;
20
+ function parseRetryAfterMs(header, defaultBackoffMs) {
21
+ if (header) {
22
+ const value = Number(header.trim());
23
+ if (Number.isFinite(value) && value >= 0) {
24
+ return Math.min(value * 1e3, RETRY_AFTER_CAP_MS);
25
+ }
26
+ }
27
+ return Math.min(defaultBackoffMs, RETRY_AFTER_CAP_MS);
28
+ }
29
+ function sleep(ms) {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
16
32
  var EngramClient = class {
17
33
  apiKey;
18
34
  baseUrl;
19
35
  fetchImpl;
20
36
  timeoutMs;
37
+ maxRetriesOn429;
21
38
  constructor(options = {}) {
22
39
  const apiKey = options.apiKey ?? (typeof process !== "undefined" ? process.env?.ENGRAM_API_KEY : void 0) ?? "";
23
40
  if (!apiKey) {
@@ -30,6 +47,7 @@ var EngramClient = class {
30
47
  this.baseUrl = baseUrl.replace(/\/+$/, "");
31
48
  this.fetchImpl = options.fetch ?? globalThis.fetch;
32
49
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
50
+ this.maxRetriesOn429 = Math.max(0, options.maxRetriesOn429 ?? DEFAULT_MAX_RETRIES_ON_429);
33
51
  if (typeof this.fetchImpl !== "function") {
34
52
  throw new Error(
35
53
  "EngramClient: no fetch implementation available. Use Node 18+, or pass options.fetch."
@@ -45,40 +63,54 @@ var EngramClient = class {
45
63
  if (v !== void 0) url.searchParams.set(k, String(v));
46
64
  }
47
65
  }
48
- const controller = new AbortController();
49
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
50
- let res;
51
- try {
52
- res = await this.fetchImpl(url.toString(), {
53
- method: init.method,
54
- headers: {
55
- Authorization: `Bearer ${this.apiKey}`,
56
- "Content-Type": "application/json"
57
- },
58
- body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
59
- signal: controller.signal
60
- });
61
- } finally {
62
- clearTimeout(timer);
63
- }
64
- const text = await res.text();
65
- let parsed = void 0;
66
- if (text) {
66
+ const requestBody = init.body !== void 0 ? JSON.stringify(init.body) : void 0;
67
+ let attemptsRemaining = this.maxRetriesOn429;
68
+ let backoffMs = 1e3;
69
+ while (true) {
70
+ const controller = new AbortController();
71
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
72
+ let res;
67
73
  try {
68
- parsed = JSON.parse(text);
69
- } catch {
70
- parsed = text;
74
+ res = await this.fetchImpl(url.toString(), {
75
+ method: init.method,
76
+ headers: {
77
+ Authorization: `Bearer ${this.apiKey}`,
78
+ "Content-Type": "application/json",
79
+ "User-Agent": USER_AGENT
80
+ },
81
+ body: requestBody,
82
+ signal: controller.signal
83
+ });
84
+ } finally {
85
+ clearTimeout(timer);
71
86
  }
87
+ if (res.status === 429 && attemptsRemaining > 0) {
88
+ await res.text().catch(() => void 0);
89
+ const delay = parseRetryAfterMs(res.headers.get("Retry-After"), backoffMs);
90
+ await sleep(delay);
91
+ attemptsRemaining -= 1;
92
+ backoffMs = Math.min(backoffMs * 2, RETRY_AFTER_CAP_MS);
93
+ continue;
94
+ }
95
+ const text = await res.text();
96
+ let parsed = void 0;
97
+ if (text) {
98
+ try {
99
+ parsed = JSON.parse(text);
100
+ } catch {
101
+ parsed = text;
102
+ }
103
+ }
104
+ if (!res.ok) {
105
+ const detail = parsed && typeof parsed === "object" && parsed !== null && "error" in parsed ? parsed.error : parsed;
106
+ throw new EngramError(
107
+ `Engram API ${res.status}: ${typeof detail === "string" ? detail : JSON.stringify(detail ?? "")}`,
108
+ res.status,
109
+ parsed
110
+ );
111
+ }
112
+ return parsed;
72
113
  }
73
- if (!res.ok) {
74
- const detail = parsed && typeof parsed === "object" && parsed !== null && "error" in parsed ? parsed.error : parsed;
75
- throw new EngramError(
76
- `Engram API ${res.status}: ${typeof detail === "string" ? detail : JSON.stringify(detail ?? "")}`,
77
- res.status,
78
- parsed
79
- );
80
- }
81
- return parsed;
82
114
  }
83
115
  // ---------- Memories ----------
84
116
  async storeMemory(content, bucket = "default") {
@@ -88,10 +120,11 @@ var EngramClient = class {
88
120
  );
89
121
  }
90
122
  async storeMemories(contents, bucket = "default") {
91
- return this.request(
123
+ const result = await this.request(
92
124
  `/v1/buckets/${encodeURIComponent(bucket)}/memories`,
93
125
  { method: "POST", body: { memories: contents.map((content) => ({ content })) } }
94
126
  );
127
+ return Array.isArray(result) ? { memories: result } : result;
95
128
  }
96
129
  async listMemories(bucket = "default", options = {}) {
97
130
  return this.request(
@@ -109,10 +142,11 @@ var EngramClient = class {
109
142
  );
110
143
  }
111
144
  async clearMemories(bucket) {
112
- await this.request(
145
+ const res = await this.request(
113
146
  `/v1/buckets/${encodeURIComponent(bucket)}/memories`,
114
147
  { method: "DELETE" }
115
148
  );
149
+ return res ?? { success: true, cleared_count: 0 };
116
150
  }
117
151
  // ---------- Query ----------
118
152
  async query(question, options = {}) {
@@ -130,6 +164,183 @@ var EngramClient = class {
130
164
  }
131
165
  });
132
166
  }
167
+ /**
168
+ * Streaming variant of {@link query}. Returns an async-iterable that
169
+ * yields {@link QueryStreamEvent} frames as the server produces them:
170
+ *
171
+ * for await (const ev of engram.queryStream('...')) {
172
+ * if (ev.type === 'delta') process.stdout.write(ev.content);
173
+ * else if (ev.type === 'done') console.log(ev.usage);
174
+ * }
175
+ *
176
+ * Break out of the loop to abort the request (the underlying
177
+ * AbortController is wired up to the fetch call).
178
+ */
179
+ queryStream(question, options = {}) {
180
+ const buckets = options.buckets ?? ["default"];
181
+ const body = {
182
+ query: question,
183
+ buckets,
184
+ stream: true,
185
+ options: {
186
+ top_k: options.topK ?? 8,
187
+ return_explanation: options.returnExplanation ?? true,
188
+ skip_synthesis: options.skipSynthesis ?? false
189
+ }
190
+ };
191
+ const url = `${this.baseUrl}/v1/query`;
192
+ const apiKey = this.apiKey;
193
+ const fetchImpl = this.fetchImpl;
194
+ const timeoutMs = this.timeoutMs;
195
+ const maxRetriesOn429 = this.maxRetriesOn429;
196
+ const bodyJson = JSON.stringify(body);
197
+ return {
198
+ [Symbol.asyncIterator]: () => {
199
+ const controller = new AbortController();
200
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
201
+ let started = false;
202
+ let reader = null;
203
+ const decoder = new TextDecoder("utf-8");
204
+ let buffer = "";
205
+ let done = false;
206
+ const queue = [];
207
+ let pendingError = null;
208
+ const ensureStarted = async () => {
209
+ if (started) return;
210
+ started = true;
211
+ let attemptsRemaining = maxRetriesOn429;
212
+ let backoffMs = 1e3;
213
+ while (true) {
214
+ const res = await fetchImpl(url, {
215
+ method: "POST",
216
+ headers: {
217
+ Authorization: `Bearer ${apiKey}`,
218
+ "Content-Type": "application/json",
219
+ Accept: "text/event-stream",
220
+ "User-Agent": USER_AGENT
221
+ },
222
+ body: bodyJson,
223
+ signal: controller.signal
224
+ });
225
+ if (res.status === 429 && attemptsRemaining > 0) {
226
+ await res.text().catch(() => void 0);
227
+ const delay = parseRetryAfterMs(res.headers.get("Retry-After"), backoffMs);
228
+ await sleep(delay);
229
+ attemptsRemaining -= 1;
230
+ backoffMs = Math.min(backoffMs * 2, RETRY_AFTER_CAP_MS);
231
+ continue;
232
+ }
233
+ if (!res.ok) {
234
+ const text = await res.text().catch(() => "");
235
+ let parsed = text;
236
+ try {
237
+ parsed = text ? JSON.parse(text) : text;
238
+ } catch {
239
+ }
240
+ const detail = parsed && typeof parsed === "object" && parsed !== null && "error" in parsed ? parsed.error : parsed;
241
+ throw new EngramError(
242
+ `Engram API ${res.status}: ${typeof detail === "string" ? detail : JSON.stringify(detail ?? "")}`,
243
+ res.status,
244
+ parsed
245
+ );
246
+ }
247
+ if (!res.body) {
248
+ throw new EngramError("Engram API: streaming response has no body", res.status, null);
249
+ }
250
+ reader = res.body.getReader();
251
+ return;
252
+ }
253
+ };
254
+ const drainBuffer = () => {
255
+ let idx;
256
+ while ((idx = buffer.indexOf("\n\n")) !== -1) {
257
+ const frame = buffer.slice(0, idx);
258
+ buffer = buffer.slice(idx + 2);
259
+ const dataLines = [];
260
+ for (const rawLine of frame.split("\n")) {
261
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
262
+ if (line.startsWith("data: ")) {
263
+ dataLines.push(line.slice(6));
264
+ } else if (line.startsWith("data:")) {
265
+ dataLines.push(line.slice(5));
266
+ }
267
+ }
268
+ if (dataLines.length === 0) continue;
269
+ const payloadStr = dataLines.join("\n");
270
+ if (payloadStr === "[DONE]") {
271
+ done = true;
272
+ return;
273
+ }
274
+ let payload;
275
+ try {
276
+ payload = JSON.parse(payloadStr);
277
+ } catch {
278
+ continue;
279
+ }
280
+ if (payload && typeof payload === "object") {
281
+ const obj = payload;
282
+ if (obj.error) {
283
+ pendingError = new EngramError(String(obj.error), 0, obj);
284
+ done = true;
285
+ return;
286
+ }
287
+ const choices = obj.choices;
288
+ if (Array.isArray(choices) && choices.length > 0) {
289
+ const delta = choices[0]?.delta?.content;
290
+ if (typeof delta === "string" && delta.length > 0) {
291
+ queue.push({ type: "delta", content: delta });
292
+ }
293
+ continue;
294
+ }
295
+ queue.push({ type: "done", ...obj });
296
+ }
297
+ }
298
+ };
299
+ return {
300
+ next: async () => {
301
+ try {
302
+ await ensureStarted();
303
+ } catch (err) {
304
+ clearTimeout(timer);
305
+ throw err;
306
+ }
307
+ while (queue.length === 0 && !done) {
308
+ if (!reader) {
309
+ clearTimeout(timer);
310
+ throw new EngramError("Engram API: stream reader missing", 0, null);
311
+ }
312
+ const chunk = await reader.read();
313
+ if (chunk.done) {
314
+ if (buffer.length > 0) {
315
+ buffer += "\n\n";
316
+ drainBuffer();
317
+ }
318
+ done = true;
319
+ break;
320
+ }
321
+ buffer += decoder.decode(chunk.value, { stream: true });
322
+ drainBuffer();
323
+ }
324
+ if (queue.length > 0) {
325
+ return { value: queue.shift(), done: false };
326
+ }
327
+ clearTimeout(timer);
328
+ if (pendingError) throw pendingError;
329
+ return { value: void 0, done: true };
330
+ },
331
+ return: async () => {
332
+ clearTimeout(timer);
333
+ controller.abort();
334
+ try {
335
+ await reader?.cancel();
336
+ } catch {
337
+ }
338
+ return { value: void 0, done: true };
339
+ }
340
+ };
341
+ }
342
+ };
343
+ }
133
344
  // ---------- Buckets ----------
134
345
  async listBuckets() {
135
346
  const result = await this.request(`/v1/buckets`, {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export interface EngramClientOptions {\n /**\n * Engram API key. Looks like `eng_live_...`. Defaults to `process.env.ENGRAM_API_KEY`.\n */\n apiKey?: string;\n /**\n * API base URL. Defaults to `process.env.ENGRAM_BASE_URL` or `https://api.lumetra.io`.\n */\n baseUrl?: string;\n /**\n * Custom fetch implementation. Defaults to the global `fetch`.\n * Useful for proxying, retry middleware, or non-Node runtimes.\n */\n fetch?: typeof fetch;\n /**\n * Request timeout in milliseconds. Defaults to 30000 (30s).\n */\n timeoutMs?: number;\n}\n\nexport interface Bucket {\n id: string;\n name: string;\n description?: string | null;\n created_at: string;\n memory_count?: number;\n}\n\nexport interface Memory {\n id: string;\n content: string;\n bucket_name?: string;\n created_at?: string;\n token_count?: number;\n}\n\nexport interface StoreMemoryResult {\n id: string;\n bucket_name: string;\n token_count: number;\n}\n\nexport interface RetrievedMemory {\n id?: string;\n content: string;\n score?: number;\n bucket?: string;\n}\n\nexport interface QueryExplanation {\n retrieved_memories?: RetrievedMemory[];\n profile?: string | null;\n graph_facts?: string[];\n}\n\nexport interface QueryUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}\n\nexport interface QueryResult {\n answer: string;\n explanation?: QueryExplanation;\n usage?: QueryUsage;\n}\n\nexport interface QueryOptions {\n /**\n * Buckets to fuse across. Defaults to `['default']`.\n */\n buckets?: string[];\n /**\n * Maximum number of memories to retrieve. Defaults to 8.\n */\n topK?: number;\n /**\n * If true, server skips the synthesis LLM call and returns retrieval-only.\n * `answer` will be an empty string in that case. Defaults to false.\n */\n skipSynthesis?: boolean;\n /**\n * Whether to populate the `explanation` field. Defaults to true.\n */\n returnExplanation?: boolean;\n}\n\nexport interface ListMemoriesOptions {\n limit?: number;\n offset?: number;\n}\n\nexport interface ListMemoriesResult {\n memories: Memory[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport class EngramError extends Error {\n override readonly name = 'EngramError';\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.status = status;\n this.body = body;\n }\n}\n","import {\n EngramError,\n type Bucket,\n type EngramClientOptions,\n type ListMemoriesOptions,\n type ListMemoriesResult,\n type QueryOptions,\n type QueryResult,\n type StoreMemoryResult,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.lumetra.io';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport class EngramClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n\n constructor(options: EngramClientOptions = {}) {\n const apiKey =\n options.apiKey ??\n (typeof process !== 'undefined' ? process.env?.ENGRAM_API_KEY : undefined) ??\n '';\n if (!apiKey) {\n throw new Error(\n 'EngramClient: apiKey is required. Pass it explicitly or set ENGRAM_API_KEY in your environment.',\n );\n }\n const baseUrl =\n options.baseUrl ??\n (typeof process !== 'undefined' ? process.env?.ENGRAM_BASE_URL : undefined) ??\n DEFAULT_BASE_URL;\n\n this.apiKey = apiKey;\n this.baseUrl = baseUrl.replace(/\\/+$/, '');\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (typeof this.fetchImpl !== 'function') {\n throw new Error(\n 'EngramClient: no fetch implementation available. Use Node 18+, or pass options.fetch.',\n );\n }\n }\n\n private async request<T>(\n path: string,\n init: { method: string; body?: unknown; query?: Record<string, string | number | undefined> } = {\n method: 'GET',\n },\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (init.query) {\n for (const [k, v] of Object.entries(init.query)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let res: Response;\n try {\n res = await this.fetchImpl(url.toString(), {\n method: init.method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n\n const text = await res.text();\n let parsed: unknown = undefined;\n if (text) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n const detail =\n parsed && typeof parsed === 'object' && parsed !== null && 'error' in parsed\n ? (parsed as { error: unknown }).error\n : parsed;\n throw new EngramError(\n `Engram API ${res.status}: ${typeof detail === 'string' ? detail : JSON.stringify(detail ?? '')}`,\n res.status,\n parsed,\n );\n }\n\n return parsed as T;\n }\n\n // ---------- Memories ----------\n\n async storeMemory(content: string, bucket: string = 'default'): Promise<StoreMemoryResult> {\n return this.request<StoreMemoryResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'POST', body: { content } },\n );\n }\n\n async storeMemories(\n contents: string[],\n bucket: string = 'default',\n ): Promise<{ memories: StoreMemoryResult[] }> {\n return this.request<{ memories: StoreMemoryResult[] }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'POST', body: { memories: contents.map((content) => ({ content })) } },\n );\n }\n\n async listMemories(\n bucket: string = 'default',\n options: ListMemoriesOptions = {},\n ): Promise<ListMemoriesResult> {\n return this.request<ListMemoriesResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n {\n method: 'GET',\n query: { limit: options.limit ?? 20, offset: options.offset ?? 0 },\n },\n );\n }\n\n async deleteMemory(memoryId: string, bucket: string = 'default'): Promise<void> {\n await this.request<unknown>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories/${encodeURIComponent(memoryId)}`,\n { method: 'DELETE' },\n );\n }\n\n async clearMemories(bucket: string): Promise<void> {\n await this.request<unknown>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'DELETE' },\n );\n }\n\n // ---------- Query ----------\n\n async query(question: string, options: QueryOptions = {}): Promise<QueryResult> {\n const buckets = options.buckets ?? ['default'];\n return this.request<QueryResult>('/v1/query', {\n method: 'POST',\n body: {\n query: question,\n buckets,\n options: {\n top_k: options.topK ?? 8,\n return_explanation: options.returnExplanation ?? true,\n skip_synthesis: options.skipSynthesis ?? false,\n },\n },\n });\n }\n\n // ---------- Buckets ----------\n\n async listBuckets(): Promise<Bucket[]> {\n const result = await this.request<{ buckets: Bucket[] } | Bucket[]>(`/v1/buckets`, {\n method: 'GET',\n });\n return Array.isArray(result) ? result : result.buckets;\n }\n\n async createBucket(name: string, description?: string): Promise<Bucket> {\n return this.request<Bucket>('/v1/buckets', {\n method: 'POST',\n body: { name, description },\n });\n }\n\n async deleteBucket(bucket: string): Promise<void> {\n await this.request<unknown>(`/v1/buckets/${encodeURIComponent(bucket)}`, {\n method: 'DELETE',\n });\n }\n\n // ---------- Profile ----------\n\n async getProfile(bucket: string = 'default'): Promise<{ profile: string | null }> {\n return this.request<{ profile: string | null }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/profile`,\n { method: 'GET' },\n );\n }\n\n async regenerateProfile(bucket: string = 'default'): Promise<{ profile: string | null }> {\n return this.request<{ profile: string | null }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/profile/regenerate`,\n { method: 'POST' },\n );\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export interface EngramClientOptions {\n /**\n * Engram API key. Looks like `eng_live_...`. Defaults to `process.env.ENGRAM_API_KEY`.\n */\n apiKey?: string;\n /**\n * API base URL. Defaults to `process.env.ENGRAM_BASE_URL` or `https://api.lumetra.io`.\n */\n baseUrl?: string;\n /**\n * Custom fetch implementation. Defaults to the global `fetch`.\n * Useful for proxying, retry middleware, or non-Node runtimes.\n */\n fetch?: typeof fetch;\n /**\n * Request timeout in milliseconds. Defaults to 30000 (30s).\n */\n timeoutMs?: number;\n /**\n * How many times to retry on a 429 (per-tenant concurrent-request cap).\n * Honors the server's `Retry-After` header, capped at 30s per sleep.\n * Defaults to 3. Set to 0 to disable retry and surface 429 as `EngramError`\n * on the first attempt.\n */\n maxRetriesOn429?: number;\n}\n\nexport interface Bucket {\n id: string;\n name: string;\n description?: string | null;\n created_at: string;\n memory_count?: number;\n}\n\nexport interface Memory {\n id: string;\n content: string;\n bucket_name?: string;\n created_at?: string;\n token_count?: number;\n}\n\nexport interface StoreMemoryResult {\n id: string;\n bucket_name: string;\n token_count: number;\n}\n\nexport interface ClearMemoriesResult {\n success: boolean;\n /** Number of memories actually deleted (server-reported). */\n cleared_count: number;\n}\n\nexport interface RetrievedMemory {\n id?: string;\n content: string;\n score?: number;\n bucket?: string;\n}\n\nexport interface QueryExplanation {\n retrieved_memories?: RetrievedMemory[];\n profile?: string | null;\n graph_facts?: string[];\n}\n\nexport interface QueryUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n}\n\nexport interface QueryResult {\n answer: string;\n explanation?: QueryExplanation;\n usage?: QueryUsage;\n}\n\n/**\n * One frame yielded by {@link EngramClient.queryStream}.\n *\n * The shape is discriminated by `type`:\n * - `delta` frames carry an incremental piece of the answer in `content`.\n * - `done` carries the final usage + (optional) explanation. Emitted\n * exactly once at the end of the stream.\n */\nexport type QueryStreamEvent =\n | { type: 'delta'; content: string }\n | {\n type: 'done';\n usage?: QueryUsage;\n synthesis_usage?: unknown;\n explanation?: QueryExplanation;\n };\n\nexport interface QueryOptions {\n /**\n * Buckets to fuse across. Defaults to `['default']`.\n */\n buckets?: string[];\n /**\n * Maximum number of memories to retrieve. Defaults to 8.\n */\n topK?: number;\n /**\n * If true, server skips the synthesis LLM call and returns retrieval-only.\n * `answer` will be an empty string in that case. Defaults to false.\n */\n skipSynthesis?: boolean;\n /**\n * Whether to populate the `explanation` field. Defaults to true.\n */\n returnExplanation?: boolean;\n}\n\nexport interface ListMemoriesOptions {\n limit?: number;\n offset?: number;\n}\n\nexport interface ListMemoriesResult {\n memories: Memory[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport class EngramError extends Error {\n override readonly name = 'EngramError';\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.status = status;\n this.body = body;\n }\n}\n","import {\n EngramError,\n type Bucket,\n type ClearMemoriesResult,\n type EngramClientOptions,\n type ListMemoriesOptions,\n type ListMemoriesResult,\n type QueryOptions,\n type QueryResult,\n type QueryStreamEvent,\n type StoreMemoryResult,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.lumetra.io';\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_RETRIES_ON_429 = 3;\n// Cap on per-attempt backoff so a misconfigured server can't force\n// callers to sleep for minutes.\nconst RETRY_AFTER_CAP_MS = 30_000;\nconst SDK_VERSION = '0.3.0';\nconst USER_AGENT = `engram-js/${SDK_VERSION}`;\n\nfunction parseRetryAfterMs(header: string | null, defaultBackoffMs: number): number {\n if (header) {\n const value = Number(header.trim());\n if (Number.isFinite(value) && value >= 0) {\n return Math.min(value * 1000, RETRY_AFTER_CAP_MS);\n }\n }\n return Math.min(defaultBackoffMs, RETRY_AFTER_CAP_MS);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport class EngramClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly maxRetriesOn429: number;\n\n constructor(options: EngramClientOptions = {}) {\n const apiKey =\n options.apiKey ??\n (typeof process !== 'undefined' ? process.env?.ENGRAM_API_KEY : undefined) ??\n '';\n if (!apiKey) {\n throw new Error(\n 'EngramClient: apiKey is required. Pass it explicitly or set ENGRAM_API_KEY in your environment.',\n );\n }\n const baseUrl =\n options.baseUrl ??\n (typeof process !== 'undefined' ? process.env?.ENGRAM_BASE_URL : undefined) ??\n DEFAULT_BASE_URL;\n\n this.apiKey = apiKey;\n this.baseUrl = baseUrl.replace(/\\/+$/, '');\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.maxRetriesOn429 = Math.max(0, options.maxRetriesOn429 ?? DEFAULT_MAX_RETRIES_ON_429);\n\n if (typeof this.fetchImpl !== 'function') {\n throw new Error(\n 'EngramClient: no fetch implementation available. Use Node 18+, or pass options.fetch.',\n );\n }\n }\n\n private async request<T>(\n path: string,\n init: { method: string; body?: unknown; query?: Record<string, string | number | undefined> } = {\n method: 'GET',\n },\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (init.query) {\n for (const [k, v] of Object.entries(init.query)) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n }\n\n // 429-aware retry. The Engram API enforces a per-tenant concurrent-\n // request cap and sets Retry-After on 429s; without retry handling,\n // bursty clients fail immediately under load. The body is JSON-\n // serialized once outside the loop so each attempt sends an\n // identical request.\n const requestBody = init.body !== undefined ? JSON.stringify(init.body) : undefined;\n let attemptsRemaining = this.maxRetriesOn429;\n let backoffMs = 1000;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let res: Response;\n try {\n res = await this.fetchImpl(url.toString(), {\n method: init.method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n 'User-Agent': USER_AGENT,\n },\n body: requestBody,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n\n if (res.status === 429 && attemptsRemaining > 0) {\n // Drain the body so the connection can return to the pool.\n await res.text().catch(() => undefined);\n const delay = parseRetryAfterMs(res.headers.get('Retry-After'), backoffMs);\n await sleep(delay);\n attemptsRemaining -= 1;\n backoffMs = Math.min(backoffMs * 2, RETRY_AFTER_CAP_MS);\n continue;\n }\n\n const text = await res.text();\n let parsed: unknown = undefined;\n if (text) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n const detail =\n parsed && typeof parsed === 'object' && parsed !== null && 'error' in parsed\n ? (parsed as { error: unknown }).error\n : parsed;\n throw new EngramError(\n `Engram API ${res.status}: ${typeof detail === 'string' ? detail : JSON.stringify(detail ?? '')}`,\n res.status,\n parsed,\n );\n }\n\n return parsed as T;\n }\n }\n\n // ---------- Memories ----------\n\n async storeMemory(content: string, bucket: string = 'default'): Promise<StoreMemoryResult> {\n return this.request<StoreMemoryResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'POST', body: { content } },\n );\n }\n\n async storeMemories(\n contents: string[],\n bucket: string = 'default',\n ): Promise<{ memories: StoreMemoryResult[] }> {\n // Defensively unwrap: depending on server version the batch endpoint\n // returns either { memories: [...] } or a bare array. Normalize to the\n // wrapped shape so callers don't have to switch on it.\n const result = await this.request<{ memories: StoreMemoryResult[] } | StoreMemoryResult[]>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'POST', body: { memories: contents.map((content) => ({ content })) } },\n );\n return Array.isArray(result) ? { memories: result } : result;\n }\n\n async listMemories(\n bucket: string = 'default',\n options: ListMemoriesOptions = {},\n ): Promise<ListMemoriesResult> {\n return this.request<ListMemoriesResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n {\n method: 'GET',\n query: { limit: options.limit ?? 20, offset: options.offset ?? 0 },\n },\n );\n }\n\n async deleteMemory(memoryId: string, bucket: string = 'default'): Promise<void> {\n await this.request<unknown>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories/${encodeURIComponent(memoryId)}`,\n { method: 'DELETE' },\n );\n }\n\n async clearMemories(bucket: string): Promise<ClearMemoriesResult> {\n const res = await this.request<ClearMemoriesResult>(\n `/v1/buckets/${encodeURIComponent(bucket)}/memories`,\n { method: 'DELETE' },\n );\n // Defensive default: older servers / proxies may strip the body. The\n // contract surface is {success, cleared_count}; if the server stayed\n // silent we still return the same shape so callers can rely on it.\n return res ?? { success: true, cleared_count: 0 };\n }\n\n // ---------- Query ----------\n\n async query(question: string, options: QueryOptions = {}): Promise<QueryResult> {\n const buckets = options.buckets ?? ['default'];\n return this.request<QueryResult>('/v1/query', {\n method: 'POST',\n body: {\n query: question,\n buckets,\n options: {\n top_k: options.topK ?? 8,\n return_explanation: options.returnExplanation ?? true,\n skip_synthesis: options.skipSynthesis ?? false,\n },\n },\n });\n }\n\n /**\n * Streaming variant of {@link query}. Returns an async-iterable that\n * yields {@link QueryStreamEvent} frames as the server produces them:\n *\n * for await (const ev of engram.queryStream('...')) {\n * if (ev.type === 'delta') process.stdout.write(ev.content);\n * else if (ev.type === 'done') console.log(ev.usage);\n * }\n *\n * Break out of the loop to abort the request (the underlying\n * AbortController is wired up to the fetch call).\n */\n queryStream(question: string, options: QueryOptions = {}): AsyncIterable<QueryStreamEvent> {\n const buckets = options.buckets ?? ['default'];\n const body = {\n query: question,\n buckets,\n stream: true,\n options: {\n top_k: options.topK ?? 8,\n return_explanation: options.returnExplanation ?? true,\n skip_synthesis: options.skipSynthesis ?? false,\n },\n };\n const url = `${this.baseUrl}/v1/query`;\n const apiKey = this.apiKey;\n const fetchImpl = this.fetchImpl;\n const timeoutMs = this.timeoutMs;\n const maxRetriesOn429 = this.maxRetriesOn429;\n const bodyJson = JSON.stringify(body);\n\n return {\n [Symbol.asyncIterator]: () => {\n const controller = new AbortController();\n // The timeout caps total wall-clock for the stream. Set generously\n // because synthesis can run 10–25s before the first byte even with\n // streaming on slow paths; clamp to the user's configured timeout.\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let started = false;\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n const decoder = new TextDecoder('utf-8');\n let buffer = '';\n let done = false;\n const queue: QueryStreamEvent[] = [];\n let pendingError: unknown = null;\n\n const ensureStarted = async (): Promise<void> => {\n if (started) return;\n started = true;\n // 429-aware retry at the connection-open stage only. Once\n // the response body starts flowing we can't resume mid-\n // stream safely.\n let attemptsRemaining = maxRetriesOn429;\n let backoffMs = 1000;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const res = await fetchImpl(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n 'User-Agent': USER_AGENT,\n },\n body: bodyJson,\n signal: controller.signal,\n });\n if (res.status === 429 && attemptsRemaining > 0) {\n await res.text().catch(() => undefined);\n const delay = parseRetryAfterMs(res.headers.get('Retry-After'), backoffMs);\n await sleep(delay);\n attemptsRemaining -= 1;\n backoffMs = Math.min(backoffMs * 2, RETRY_AFTER_CAP_MS);\n continue;\n }\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n let parsed: unknown = text;\n try {\n parsed = text ? JSON.parse(text) : text;\n } catch {\n /* keep raw text */\n }\n const detail =\n parsed && typeof parsed === 'object' && parsed !== null && 'error' in parsed\n ? (parsed as { error: unknown }).error\n : parsed;\n throw new EngramError(\n `Engram API ${res.status}: ${typeof detail === 'string' ? detail : JSON.stringify(detail ?? '')}`,\n res.status,\n parsed,\n );\n }\n if (!res.body) {\n throw new EngramError('Engram API: streaming response has no body', res.status, null);\n }\n reader = res.body.getReader();\n return;\n }\n };\n\n const drainBuffer = (): void => {\n // SSE frames are separated by a blank line ('\\n\\n'). Each frame\n // is one or more 'field: value' lines. We only care about\n // 'data:' lines for this stream.\n let idx: number;\n while ((idx = buffer.indexOf('\\n\\n')) !== -1) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n const dataLines: string[] = [];\n for (const rawLine of frame.split('\\n')) {\n const line = rawLine.endsWith('\\r') ? rawLine.slice(0, -1) : rawLine;\n if (line.startsWith('data: ')) {\n dataLines.push(line.slice(6));\n } else if (line.startsWith('data:')) {\n dataLines.push(line.slice(5));\n }\n }\n if (dataLines.length === 0) continue;\n const payloadStr = dataLines.join('\\n');\n if (payloadStr === '[DONE]') {\n done = true;\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(payloadStr);\n } catch {\n // Malformed frame — skip rather than corrupt the stream.\n continue;\n }\n if (payload && typeof payload === 'object') {\n const obj = payload as Record<string, unknown>;\n if (obj.error) {\n pendingError = new EngramError(String(obj.error), 0, obj);\n done = true;\n return;\n }\n // OpenAI-style delta chunk\n const choices = obj.choices as Array<{ delta?: { content?: string } }> | undefined;\n if (Array.isArray(choices) && choices.length > 0) {\n const delta = choices[0]?.delta?.content;\n if (typeof delta === 'string' && delta.length > 0) {\n queue.push({ type: 'delta', content: delta });\n }\n continue;\n }\n // Final usage / explanation frame (no 'choices' key).\n queue.push({ type: 'done', ...(obj as Omit<Extract<QueryStreamEvent, { type: 'done' }>, 'type'>) });\n }\n }\n };\n\n return {\n next: async (): Promise<IteratorResult<QueryStreamEvent>> => {\n try {\n await ensureStarted();\n } catch (err) {\n clearTimeout(timer);\n throw err;\n }\n while (queue.length === 0 && !done) {\n if (!reader) {\n clearTimeout(timer);\n throw new EngramError('Engram API: stream reader missing', 0, null);\n }\n const chunk = await reader.read();\n if (chunk.done) {\n // Flush whatever's left in the buffer (some servers don't\n // terminate with a trailing blank line).\n if (buffer.length > 0) {\n buffer += '\\n\\n';\n drainBuffer();\n }\n done = true;\n break;\n }\n buffer += decoder.decode(chunk.value, { stream: true });\n drainBuffer();\n }\n if (queue.length > 0) {\n return { value: queue.shift() as QueryStreamEvent, done: false };\n }\n clearTimeout(timer);\n if (pendingError) throw pendingError;\n return { value: undefined as unknown as QueryStreamEvent, done: true };\n },\n return: async (): Promise<IteratorResult<QueryStreamEvent>> => {\n // Caller broke out of the for-await loop — cancel the upstream\n // request so we're not holding the connection open.\n clearTimeout(timer);\n controller.abort();\n try {\n await reader?.cancel();\n } catch {\n /* ignored */\n }\n return { value: undefined as unknown as QueryStreamEvent, done: true };\n },\n };\n },\n };\n }\n\n // ---------- Buckets ----------\n\n async listBuckets(): Promise<Bucket[]> {\n const result = await this.request<{ buckets: Bucket[] } | Bucket[]>(`/v1/buckets`, {\n method: 'GET',\n });\n return Array.isArray(result) ? result : result.buckets;\n }\n\n async createBucket(name: string, description?: string): Promise<Bucket> {\n return this.request<Bucket>('/v1/buckets', {\n method: 'POST',\n body: { name, description },\n });\n }\n\n async deleteBucket(bucket: string): Promise<void> {\n await this.request<unknown>(`/v1/buckets/${encodeURIComponent(bucket)}`, {\n method: 'DELETE',\n });\n }\n\n // ---------- Profile ----------\n\n async getProfile(bucket: string = 'default'): Promise<{ profile: string | null }> {\n return this.request<{ profile: string | null }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/profile`,\n { method: 'GET' },\n );\n }\n\n async regenerateProfile(bucket: string = 'default'): Promise<{ profile: string | null }> {\n return this.request<{ profile: string | null }>(\n `/v1/buckets/${encodeURIComponent(bucket)}/profile/regenerate`,\n { method: 'POST' },\n );\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumetra/engram",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Official TypeScript client for Engram — durable, explainable memory for AI agents.",
5
5
  "keywords": [
6
6
  "engram",
@@ -47,8 +47,9 @@
47
47
  "build": "tsup",
48
48
  "dev": "tsup --watch",
49
49
  "typecheck": "tsc --noEmit",
50
+ "test": "npm run build && node --test test/*.test.mjs",
50
51
  "clean": "rm -rf dist",
51
- "prepublishOnly": "npm run clean && npm run typecheck && npm run build"
52
+ "prepublishOnly": "npm run clean && npm run typecheck && npm run build && npm test"
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/node": "^20.11.0",