@memorylayerai/sdk 0.3.1 → 0.5.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
@@ -28,30 +28,30 @@ yarn add @memorylayerai/sdk
28
28
 
29
29
  ## Quick Start
30
30
 
31
- ### Option 1: Transparent Router (Zero Code Changes)
31
+ ### Option 1: Transparent Router (Beta) - Drop-in OpenAI Proxy
32
32
 
33
- The easiest way to add memory - just change your OpenAI baseURL:
33
+ Change your baseURL to add automatic memory injection:
34
34
 
35
35
  ```typescript
36
36
  import OpenAI from 'openai';
37
37
 
38
38
  const openai = new OpenAI({
39
- baseURL: 'https://api.memorylayer.ai/v1', // ← Just change this
39
+ baseURL: 'https://api.memorylayer.ai/v1', // ← Point to MemoryLayer
40
40
  apiKey: 'ml_your_memorylayer_key' // ← Use your MemoryLayer key
41
41
  });
42
42
 
43
- // That's it! Memory is automatically injected
43
+ // Memory is automatically retrieved and injected
44
44
  const response = await openai.chat.completions.create({
45
45
  model: 'gpt-4',
46
46
  messages: [{ role: 'user', content: 'What are my preferences?' }]
47
47
  });
48
48
  ```
49
49
 
50
- **Benefits:**
51
- - ✅ Zero code changes to your application
52
- - ✅ Automatic memory injection
53
- - ✅ Works with existing OpenAI SDK code
54
- - Configurable via headers
50
+ **Current Status:**
51
+ - ✅ Works with `/v1/chat/completions` (non-streaming)
52
+ - ✅ OpenAI-compatible responses
53
+ - ✅ Configurable via headers (use `fetch`/`axios` for guaranteed header support)
54
+ - Streaming support coming soon
55
55
 
56
56
  See [Transparent Router Guide](#transparent-router) for details.
57
57
 
@@ -115,7 +115,12 @@ results.forEach(result => {
115
115
 
116
116
  ## Transparent Router
117
117
 
118
- The transparent router is an OpenAI-compatible proxy that automatically injects memory context into your requests. It's the easiest way to add memory to your application.
118
+ The transparent router is an OpenAI-compatible proxy that automatically injects memory context into your requests.
119
+
120
+ **Current Status:**
121
+ - ✅ Works with `/v1/chat/completions` (non-streaming)
122
+ - ✅ OpenAI-compatible responses
123
+ - ⏳ Streaming support coming soon
119
124
 
120
125
  ### Basic Usage
121
126
 
@@ -135,67 +140,52 @@ const response = await openai.chat.completions.create({
135
140
 
136
141
  ### Configuration Headers
137
142
 
138
- Control memory injection with optional headers:
143
+ Control memory injection with optional headers. **Note:** For guaranteed header support, use `fetch` or `axios` directly:
139
144
 
140
145
  ```typescript
141
- const response = await openai.chat.completions.create({
142
- model: 'gpt-4',
143
- messages: [{ role: 'user', content: 'Hello!' }],
146
+ // Using fetch for guaranteed header support
147
+ const response = await fetch('https://api.memorylayer.ai/v1/chat/completions', {
148
+ method: 'POST',
144
149
  headers: {
145
- 'x-memory-user-id': 'user_123', // User scope
146
- 'x-memory-session-id': 'sess_abc', // Session scope
147
- 'x-memory-limit': '10', // Max memories
148
- 'x-memory-injection-mode': 'balanced', // safe|balanced|full
149
- 'x-memory-injection-strategy': 'system_append', // Injection strategy
150
- 'x-memory-disabled': 'false' // Enable/disable
151
- }
150
+ 'Authorization': `Bearer ${process.env.MEMORYLAYER_API_KEY}`,
151
+ 'Content-Type': 'application/json',
152
+ 'x-memory-user-id': 'user_123', // User scope (required for multi-user apps)
153
+ 'x-memory-session-id': 'sess_abc', // Session scope (persist from response)
154
+ 'x-memory-limit': '10', // Max memories to inject
155
+ 'x-memory-injection-mode': 'safe', // safe|full (balanced coming soon)
156
+ 'x-memory-disabled': 'false' // Enable/disable memory
157
+ },
158
+ body: JSON.stringify({
159
+ model: 'gpt-4',
160
+ messages: [{ role: 'user', content: 'Hello!' }]
161
+ })
152
162
  });
153
163
  ```
154
164
 
155
165
  ### Injection Modes
156
166
 
157
- - **safe**: Only fact + preference (minimal risk)
158
- - **balanced** (default): fact + preference + trusted summaries
159
- - **full**: All memory types including snippets
167
+ - **safe** (default): Only fact + preference types (minimal risk, structured data)
168
+ - **full**: All memory types including snippets (maximum context, higher token usage)
169
+ - **balanced**: Trusted summaries + facts + preferences (coming soon)
160
170
 
161
171
  ### Diagnostic Headers
162
172
 
163
- Every response includes diagnostic headers:
173
+ Every response includes diagnostic headers showing what happened:
164
174
 
165
175
  ```typescript
166
- const response = await openai.chat.completions.create({ ... });
167
-
168
- console.log('Memories retrieved:', response.headers?.['x-memory-hit-count']);
169
- console.log('Tokens injected:', response.headers?.['x-memory-injected-tokens']);
170
- console.log('Max score:', response.headers?.['x-memory-max-score']);
171
- console.log('Query rewriting:', response.headers?.['x-memory-rewrite']);
172
- console.log('Memory status:', response.headers?.['x-memory-status']);
173
- console.log('Session ID:', response.headers?.['x-memory-session-id']);
174
- ```
175
-
176
- ### Streaming Support
176
+ const response = await fetch('https://api.memorylayer.ai/v1/chat/completions', { ... });
177
177
 
178
- Streaming works seamlessly:
179
-
180
- ```typescript
181
- const stream = await openai.chat.completions.create({
182
- model: 'gpt-4',
183
- messages: [{ role: 'user', content: 'Tell me about myself' }],
184
- stream: true,
185
- headers: {
186
- 'x-memory-user-id': 'user_123'
187
- }
188
- });
189
-
190
- for await (const chunk of stream) {
191
- const content = chunk.choices[0]?.delta?.content || '';
192
- process.stdout.write(content);
193
- }
178
+ console.log('Memories retrieved:', response.headers.get('x-memory-hit-count'));
179
+ console.log('Tokens injected:', response.headers.get('x-memory-injected-tokens'));
180
+ console.log('Max score:', response.headers.get('x-memory-max-score'));
181
+ console.log('Query rewriting:', response.headers.get('x-memory-rewrite'));
182
+ console.log('Memory status:', response.headers.get('x-memory-status'));
183
+ console.log('Session ID:', response.headers.get('x-memory-session-id')); // Persist this!
194
184
  ```
195
185
 
196
186
  ### Session Management
197
187
 
198
- If you don't provide `x-memory-user-id` or `x-memory-session-id`, the router generates a session ID. Persist it for conversation continuity:
188
+ For chat applications, persist `x-memory-session-id` from response headers and pass it in subsequent requests:
199
189
 
200
190
  ```typescript
201
191
  const response = await openai.chat.completions.create({ ... });
package/dist/index.cjs CHANGED
@@ -221,9 +221,18 @@ var init_search = __esm({
221
221
  this.httpClient = httpClient;
222
222
  }
223
223
  /**
224
- * Search memories
224
+ * Search memories using the unified /v1/search endpoint with hybrid retrieval.
225
+ *
226
+ * This uses the app's full retrieval pipeline with:
227
+ * - Vector similarity search
228
+ * - BM25 keyword search
229
+ * - Recency scoring
230
+ * - Graph connectivity (optional)
231
+ * - Entity expansion (optional)
232
+ * - LLM/Cross-encoder reranking (optional)
233
+ *
225
234
  * @param request - Search request
226
- * @returns Search results
235
+ * @returns Search results with memory pack structure
227
236
  */
228
237
  async search(request) {
229
238
  if (!request.query || request.query.trim().length === 0) {
@@ -238,24 +247,36 @@ var init_search = __esm({
238
247
  [{ field: "projectId", message: "Project ID is required" }]
239
248
  );
240
249
  }
241
- const query = {
242
- q: request.query,
243
- projectId: request.projectId
250
+ const body = {
251
+ query: request.query,
252
+ project_id: request.projectId,
253
+ include_text_format: true
244
254
  };
245
255
  if (request.limit !== void 0) {
246
- query.limit = request.limit.toString();
247
- }
248
- if (request.threshold !== void 0) {
249
- query.threshold = request.threshold.toString();
250
- }
251
- if (request.filter) {
252
- query.filter = JSON.stringify(request.filter);
256
+ body.limit = request.limit;
253
257
  }
254
- return this.httpClient.request({
255
- method: "GET",
258
+ body.rerank_strategy = request.rerankingStrategy || "cross-encoder";
259
+ const response = await this.httpClient.request({
260
+ method: "POST",
256
261
  path: "/v1/search",
257
- query
262
+ body
258
263
  });
264
+ const memoryPack = response.memory_pack || {};
265
+ const results = [];
266
+ for (const memoryType of ["facts", "preferences", "entities", "sources"]) {
267
+ const items = memoryPack[memoryType] || [];
268
+ for (const item of items) {
269
+ results.push({
270
+ memory: item,
271
+ score: item.score || 1,
272
+ highlights: item.highlights || []
273
+ });
274
+ }
275
+ }
276
+ return {
277
+ results,
278
+ total: results.length
279
+ };
259
280
  }
260
281
  };
261
282
  }
@@ -293,18 +314,15 @@ var init_ingest = __esm({
293
314
  [{ field: "projectId", message: "Project ID is required" }]
294
315
  );
295
316
  }
296
- const body = {
297
- projectId: request.projectId,
298
- metadata: request.metadata,
299
- chunkSize: request.chunkSize,
300
- chunkOverlap: request.chunkOverlap,
301
- // In a real implementation, you'd convert the file to base64 or use FormData
302
- file: request.file
303
- };
304
317
  return this.httpClient.request({
305
318
  method: "POST",
306
- path: "/v1/ingest/file",
307
- body
319
+ path: "/v1/ingest",
320
+ body: {
321
+ type: "pdf",
322
+ projectId: request.projectId,
323
+ metadata: request.metadata || {},
324
+ file: request.file
325
+ }
308
326
  });
309
327
  }
310
328
  /**
@@ -327,9 +345,71 @@ var init_ingest = __esm({
327
345
  }
328
346
  return this.httpClient.request({
329
347
  method: "POST",
330
- path: "/v1/ingest/text",
331
- body: request
348
+ path: "/v1/ingest",
349
+ body: {
350
+ type: "text",
351
+ content: request.text,
352
+ projectId: request.projectId,
353
+ metadata: request.metadata || {}
354
+ }
355
+ });
356
+ }
357
+ /**
358
+ * Ingest content from a URL
359
+ * @param url - URL to ingest from
360
+ * @param projectId - Project ID
361
+ * @param metadata - Optional metadata
362
+ * @returns Ingestion response with job details
363
+ */
364
+ async url(url, projectId, metadata) {
365
+ if (!url || url.trim().length === 0) {
366
+ throw new ValidationError(
367
+ "URL cannot be empty",
368
+ [{ field: "url", message: "URL is required and cannot be empty" }]
369
+ );
370
+ }
371
+ if (!projectId || projectId.trim().length === 0) {
372
+ throw new ValidationError(
373
+ "Project ID is required",
374
+ [{ field: "projectId", message: "Project ID is required" }]
375
+ );
376
+ }
377
+ return this.httpClient.request({
378
+ method: "POST",
379
+ path: "/v1/ingest",
380
+ body: {
381
+ type: "url",
382
+ url,
383
+ projectId,
384
+ metadata: metadata || {}
385
+ }
386
+ });
387
+ }
388
+ /**
389
+ * Get the status of an ingestion job
390
+ * @param jobId - Job ID returned from ingest
391
+ * @param projectId - Project ID
392
+ * @returns Job status information
393
+ */
394
+ async getJob(jobId, projectId) {
395
+ if (!jobId || jobId.trim().length === 0) {
396
+ throw new ValidationError(
397
+ "Job ID is required",
398
+ [{ field: "jobId", message: "Job ID is required" }]
399
+ );
400
+ }
401
+ if (!projectId || projectId.trim().length === 0) {
402
+ throw new ValidationError(
403
+ "Project ID is required",
404
+ [{ field: "projectId", message: "Project ID is required" }]
405
+ );
406
+ }
407
+ const response = await this.httpClient.request({
408
+ method: "GET",
409
+ path: `/v1/jobs/${jobId}`,
410
+ query: { projectId }
332
411
  });
412
+ return response.data || response;
333
413
  }
334
414
  };
335
415
  }
package/dist/index.d.cts CHANGED
@@ -163,12 +163,30 @@ interface SearchRequest {
163
163
  query: string;
164
164
  /** Project ID to search in */
165
165
  projectId: string;
166
- /** Maximum number of results to return (default: 10) */
166
+ /** Maximum number of results to return (default: 10 - supermemory production default) */
167
167
  limit?: number;
168
168
  /** Filter criteria for search */
169
169
  filter?: Record<string, any>;
170
- /** Minimum relevance score threshold (0-1) */
170
+ /** Minimum relevance score threshold (0-1, default: 0.6 - supermemory production default for broad recall) */
171
171
  threshold?: number;
172
+ /** Enable query rewriting (default: false - adds ~400ms latency) */
173
+ enableQueryRewriting?: boolean;
174
+ /** Enable entity expansion search (default: false) */
175
+ enableEntityExpansion?: boolean;
176
+ /** Enable graph connectivity search (default: false) */
177
+ enableGraphConnectivity?: boolean;
178
+ /** Enable semantic deduplication (default: false) */
179
+ enableSemanticDedup?: boolean;
180
+ /** Reranking strategy: 'none', 'cross-encoder', 'llm' (default: 'none' - adds latency) */
181
+ rerankingStrategy?: 'none' | 'cross-encoder' | 'llm';
182
+ /** Custom fusion weights for multi-method retrieval */
183
+ fusionWeights?: {
184
+ vector?: number;
185
+ bm25?: number;
186
+ recency?: number;
187
+ entity?: number;
188
+ graph?: number;
189
+ };
172
190
  }
173
191
  /**
174
192
  * Search result
@@ -180,6 +198,20 @@ interface SearchResult {
180
198
  score: number;
181
199
  /** Highlighted text snippets */
182
200
  highlights?: string[];
201
+ /** Score breakdown by retrieval method */
202
+ scoreBreakdown?: {
203
+ vectorScore?: number;
204
+ bm25Score?: number;
205
+ recencyScore?: number;
206
+ entityScore?: number;
207
+ graphScore?: number;
208
+ };
209
+ /** Connected memories (if graph enhancement enabled) */
210
+ connections?: Array<{
211
+ memoryId: string;
212
+ connectionType: 'updates' | 'extends' | 'derives' | 'similarity';
213
+ connectionStrength: number;
214
+ }>;
183
215
  }
184
216
  /**
185
217
  * Search response
@@ -200,9 +232,9 @@ interface IngestFileRequest {
200
232
  projectId: string;
201
233
  /** Optional metadata to associate with ingested memories */
202
234
  metadata?: Record<string, any>;
203
- /** Chunk size for splitting the file (default: 1000) */
235
+ /** Chunk size for splitting the file (default: 512 tokens - supermemory production default) */
204
236
  chunkSize?: number;
205
- /** Overlap between chunks (default: 200) */
237
+ /** Overlap between chunks (default: 10% - supermemory production default) */
206
238
  chunkOverlap?: number;
207
239
  }
208
240
  /**
@@ -215,9 +247,9 @@ interface IngestTextRequest {
215
247
  projectId: string;
216
248
  /** Optional metadata to associate with ingested memories */
217
249
  metadata?: Record<string, any>;
218
- /** Chunk size for splitting the text (default: 1000) */
250
+ /** Chunk size for splitting the text (default: 512 tokens - supermemory production default) */
219
251
  chunkSize?: number;
220
- /** Overlap between chunks (default: 200) */
252
+ /** Overlap between chunks (default: 10% - supermemory production default) */
221
253
  chunkOverlap?: number;
222
254
  }
223
255
  /**
@@ -246,11 +278,11 @@ interface RouterRequest {
246
278
  messages: Message[];
247
279
  /** Project ID for memory context */
248
280
  projectId: string;
249
- /** Model to use (optional) */
281
+ /** Model to use (default: 'gpt-4o-mini' - supermemory production default) */
250
282
  model?: string;
251
- /** Temperature for generation (0-2, default: 1) */
283
+ /** Temperature for generation (0-2, default: 0.7 - supermemory production default) */
252
284
  temperature?: number;
253
- /** Maximum tokens to generate */
285
+ /** Maximum tokens to generate (default: 2000 - supermemory production default) */
254
286
  maxTokens?: number;
255
287
  /** Whether to stream the response */
256
288
  stream?: boolean;
@@ -612,9 +644,18 @@ declare class SearchResource {
612
644
  private httpClient;
613
645
  constructor(httpClient: HTTPClient);
614
646
  /**
615
- * Search memories
647
+ * Search memories using the unified /v1/search endpoint with hybrid retrieval.
648
+ *
649
+ * This uses the app's full retrieval pipeline with:
650
+ * - Vector similarity search
651
+ * - BM25 keyword search
652
+ * - Recency scoring
653
+ * - Graph connectivity (optional)
654
+ * - Entity expansion (optional)
655
+ * - LLM/Cross-encoder reranking (optional)
656
+ *
616
657
  * @param request - Search request
617
- * @returns Search results
658
+ * @returns Search results with memory pack structure
618
659
  */
619
660
  search(request: SearchRequest): Promise<SearchResponse>;
620
661
  }
@@ -637,6 +678,21 @@ declare class IngestResource {
637
678
  * @returns Ingestion response with created memory IDs
638
679
  */
639
680
  text(request: IngestTextRequest): Promise<IngestResponse>;
681
+ /**
682
+ * Ingest content from a URL
683
+ * @param url - URL to ingest from
684
+ * @param projectId - Project ID
685
+ * @param metadata - Optional metadata
686
+ * @returns Ingestion response with job details
687
+ */
688
+ url(url: string, projectId: string, metadata?: Record<string, any>): Promise<IngestResponse>;
689
+ /**
690
+ * Get the status of an ingestion job
691
+ * @param jobId - Job ID returned from ingest
692
+ * @param projectId - Project ID
693
+ * @returns Job status information
694
+ */
695
+ getJob(jobId: string, projectId: string): Promise<any>;
640
696
  }
641
697
 
642
698
  /**
package/dist/index.d.ts CHANGED
@@ -163,12 +163,30 @@ interface SearchRequest {
163
163
  query: string;
164
164
  /** Project ID to search in */
165
165
  projectId: string;
166
- /** Maximum number of results to return (default: 10) */
166
+ /** Maximum number of results to return (default: 10 - supermemory production default) */
167
167
  limit?: number;
168
168
  /** Filter criteria for search */
169
169
  filter?: Record<string, any>;
170
- /** Minimum relevance score threshold (0-1) */
170
+ /** Minimum relevance score threshold (0-1, default: 0.6 - supermemory production default for broad recall) */
171
171
  threshold?: number;
172
+ /** Enable query rewriting (default: false - adds ~400ms latency) */
173
+ enableQueryRewriting?: boolean;
174
+ /** Enable entity expansion search (default: false) */
175
+ enableEntityExpansion?: boolean;
176
+ /** Enable graph connectivity search (default: false) */
177
+ enableGraphConnectivity?: boolean;
178
+ /** Enable semantic deduplication (default: false) */
179
+ enableSemanticDedup?: boolean;
180
+ /** Reranking strategy: 'none', 'cross-encoder', 'llm' (default: 'none' - adds latency) */
181
+ rerankingStrategy?: 'none' | 'cross-encoder' | 'llm';
182
+ /** Custom fusion weights for multi-method retrieval */
183
+ fusionWeights?: {
184
+ vector?: number;
185
+ bm25?: number;
186
+ recency?: number;
187
+ entity?: number;
188
+ graph?: number;
189
+ };
172
190
  }
173
191
  /**
174
192
  * Search result
@@ -180,6 +198,20 @@ interface SearchResult {
180
198
  score: number;
181
199
  /** Highlighted text snippets */
182
200
  highlights?: string[];
201
+ /** Score breakdown by retrieval method */
202
+ scoreBreakdown?: {
203
+ vectorScore?: number;
204
+ bm25Score?: number;
205
+ recencyScore?: number;
206
+ entityScore?: number;
207
+ graphScore?: number;
208
+ };
209
+ /** Connected memories (if graph enhancement enabled) */
210
+ connections?: Array<{
211
+ memoryId: string;
212
+ connectionType: 'updates' | 'extends' | 'derives' | 'similarity';
213
+ connectionStrength: number;
214
+ }>;
183
215
  }
184
216
  /**
185
217
  * Search response
@@ -200,9 +232,9 @@ interface IngestFileRequest {
200
232
  projectId: string;
201
233
  /** Optional metadata to associate with ingested memories */
202
234
  metadata?: Record<string, any>;
203
- /** Chunk size for splitting the file (default: 1000) */
235
+ /** Chunk size for splitting the file (default: 512 tokens - supermemory production default) */
204
236
  chunkSize?: number;
205
- /** Overlap between chunks (default: 200) */
237
+ /** Overlap between chunks (default: 10% - supermemory production default) */
206
238
  chunkOverlap?: number;
207
239
  }
208
240
  /**
@@ -215,9 +247,9 @@ interface IngestTextRequest {
215
247
  projectId: string;
216
248
  /** Optional metadata to associate with ingested memories */
217
249
  metadata?: Record<string, any>;
218
- /** Chunk size for splitting the text (default: 1000) */
250
+ /** Chunk size for splitting the text (default: 512 tokens - supermemory production default) */
219
251
  chunkSize?: number;
220
- /** Overlap between chunks (default: 200) */
252
+ /** Overlap between chunks (default: 10% - supermemory production default) */
221
253
  chunkOverlap?: number;
222
254
  }
223
255
  /**
@@ -246,11 +278,11 @@ interface RouterRequest {
246
278
  messages: Message[];
247
279
  /** Project ID for memory context */
248
280
  projectId: string;
249
- /** Model to use (optional) */
281
+ /** Model to use (default: 'gpt-4o-mini' - supermemory production default) */
250
282
  model?: string;
251
- /** Temperature for generation (0-2, default: 1) */
283
+ /** Temperature for generation (0-2, default: 0.7 - supermemory production default) */
252
284
  temperature?: number;
253
- /** Maximum tokens to generate */
285
+ /** Maximum tokens to generate (default: 2000 - supermemory production default) */
254
286
  maxTokens?: number;
255
287
  /** Whether to stream the response */
256
288
  stream?: boolean;
@@ -612,9 +644,18 @@ declare class SearchResource {
612
644
  private httpClient;
613
645
  constructor(httpClient: HTTPClient);
614
646
  /**
615
- * Search memories
647
+ * Search memories using the unified /v1/search endpoint with hybrid retrieval.
648
+ *
649
+ * This uses the app's full retrieval pipeline with:
650
+ * - Vector similarity search
651
+ * - BM25 keyword search
652
+ * - Recency scoring
653
+ * - Graph connectivity (optional)
654
+ * - Entity expansion (optional)
655
+ * - LLM/Cross-encoder reranking (optional)
656
+ *
616
657
  * @param request - Search request
617
- * @returns Search results
658
+ * @returns Search results with memory pack structure
618
659
  */
619
660
  search(request: SearchRequest): Promise<SearchResponse>;
620
661
  }
@@ -637,6 +678,21 @@ declare class IngestResource {
637
678
  * @returns Ingestion response with created memory IDs
638
679
  */
639
680
  text(request: IngestTextRequest): Promise<IngestResponse>;
681
+ /**
682
+ * Ingest content from a URL
683
+ * @param url - URL to ingest from
684
+ * @param projectId - Project ID
685
+ * @param metadata - Optional metadata
686
+ * @returns Ingestion response with job details
687
+ */
688
+ url(url: string, projectId: string, metadata?: Record<string, any>): Promise<IngestResponse>;
689
+ /**
690
+ * Get the status of an ingestion job
691
+ * @param jobId - Job ID returned from ingest
692
+ * @param projectId - Project ID
693
+ * @returns Job status information
694
+ */
695
+ getJob(jobId: string, projectId: string): Promise<any>;
640
696
  }
641
697
 
642
698
  /**
package/dist/index.js CHANGED
@@ -220,9 +220,18 @@ var init_search = __esm({
220
220
  this.httpClient = httpClient;
221
221
  }
222
222
  /**
223
- * Search memories
223
+ * Search memories using the unified /v1/search endpoint with hybrid retrieval.
224
+ *
225
+ * This uses the app's full retrieval pipeline with:
226
+ * - Vector similarity search
227
+ * - BM25 keyword search
228
+ * - Recency scoring
229
+ * - Graph connectivity (optional)
230
+ * - Entity expansion (optional)
231
+ * - LLM/Cross-encoder reranking (optional)
232
+ *
224
233
  * @param request - Search request
225
- * @returns Search results
234
+ * @returns Search results with memory pack structure
226
235
  */
227
236
  async search(request) {
228
237
  if (!request.query || request.query.trim().length === 0) {
@@ -237,24 +246,36 @@ var init_search = __esm({
237
246
  [{ field: "projectId", message: "Project ID is required" }]
238
247
  );
239
248
  }
240
- const query = {
241
- q: request.query,
242
- projectId: request.projectId
249
+ const body = {
250
+ query: request.query,
251
+ project_id: request.projectId,
252
+ include_text_format: true
243
253
  };
244
254
  if (request.limit !== void 0) {
245
- query.limit = request.limit.toString();
246
- }
247
- if (request.threshold !== void 0) {
248
- query.threshold = request.threshold.toString();
249
- }
250
- if (request.filter) {
251
- query.filter = JSON.stringify(request.filter);
255
+ body.limit = request.limit;
252
256
  }
253
- return this.httpClient.request({
254
- method: "GET",
257
+ body.rerank_strategy = request.rerankingStrategy || "cross-encoder";
258
+ const response = await this.httpClient.request({
259
+ method: "POST",
255
260
  path: "/v1/search",
256
- query
261
+ body
257
262
  });
263
+ const memoryPack = response.memory_pack || {};
264
+ const results = [];
265
+ for (const memoryType of ["facts", "preferences", "entities", "sources"]) {
266
+ const items = memoryPack[memoryType] || [];
267
+ for (const item of items) {
268
+ results.push({
269
+ memory: item,
270
+ score: item.score || 1,
271
+ highlights: item.highlights || []
272
+ });
273
+ }
274
+ }
275
+ return {
276
+ results,
277
+ total: results.length
278
+ };
258
279
  }
259
280
  };
260
281
  }
@@ -292,18 +313,15 @@ var init_ingest = __esm({
292
313
  [{ field: "projectId", message: "Project ID is required" }]
293
314
  );
294
315
  }
295
- const body = {
296
- projectId: request.projectId,
297
- metadata: request.metadata,
298
- chunkSize: request.chunkSize,
299
- chunkOverlap: request.chunkOverlap,
300
- // In a real implementation, you'd convert the file to base64 or use FormData
301
- file: request.file
302
- };
303
316
  return this.httpClient.request({
304
317
  method: "POST",
305
- path: "/v1/ingest/file",
306
- body
318
+ path: "/v1/ingest",
319
+ body: {
320
+ type: "pdf",
321
+ projectId: request.projectId,
322
+ metadata: request.metadata || {},
323
+ file: request.file
324
+ }
307
325
  });
308
326
  }
309
327
  /**
@@ -326,9 +344,71 @@ var init_ingest = __esm({
326
344
  }
327
345
  return this.httpClient.request({
328
346
  method: "POST",
329
- path: "/v1/ingest/text",
330
- body: request
347
+ path: "/v1/ingest",
348
+ body: {
349
+ type: "text",
350
+ content: request.text,
351
+ projectId: request.projectId,
352
+ metadata: request.metadata || {}
353
+ }
354
+ });
355
+ }
356
+ /**
357
+ * Ingest content from a URL
358
+ * @param url - URL to ingest from
359
+ * @param projectId - Project ID
360
+ * @param metadata - Optional metadata
361
+ * @returns Ingestion response with job details
362
+ */
363
+ async url(url, projectId, metadata) {
364
+ if (!url || url.trim().length === 0) {
365
+ throw new ValidationError(
366
+ "URL cannot be empty",
367
+ [{ field: "url", message: "URL is required and cannot be empty" }]
368
+ );
369
+ }
370
+ if (!projectId || projectId.trim().length === 0) {
371
+ throw new ValidationError(
372
+ "Project ID is required",
373
+ [{ field: "projectId", message: "Project ID is required" }]
374
+ );
375
+ }
376
+ return this.httpClient.request({
377
+ method: "POST",
378
+ path: "/v1/ingest",
379
+ body: {
380
+ type: "url",
381
+ url,
382
+ projectId,
383
+ metadata: metadata || {}
384
+ }
385
+ });
386
+ }
387
+ /**
388
+ * Get the status of an ingestion job
389
+ * @param jobId - Job ID returned from ingest
390
+ * @param projectId - Project ID
391
+ * @returns Job status information
392
+ */
393
+ async getJob(jobId, projectId) {
394
+ if (!jobId || jobId.trim().length === 0) {
395
+ throw new ValidationError(
396
+ "Job ID is required",
397
+ [{ field: "jobId", message: "Job ID is required" }]
398
+ );
399
+ }
400
+ if (!projectId || projectId.trim().length === 0) {
401
+ throw new ValidationError(
402
+ "Project ID is required",
403
+ [{ field: "projectId", message: "Project ID is required" }]
404
+ );
405
+ }
406
+ const response = await this.httpClient.request({
407
+ method: "GET",
408
+ path: `/v1/jobs/${jobId}`,
409
+ query: { projectId }
331
410
  });
411
+ return response.data || response;
332
412
  }
333
413
  };
334
414
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorylayerai/sdk",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "Official Node.js/TypeScript SDK for MemoryLayer",
6
6
  "main": "dist/index.js",
@@ -29,21 +29,16 @@ export class IngestResource {
29
29
  );
30
30
  }
31
31
 
32
- // For file uploads, we need to use FormData
33
- // This is a simplified implementation - in production, you'd handle multipart/form-data properly
34
- const body = {
35
- projectId: request.projectId,
36
- metadata: request.metadata,
37
- chunkSize: request.chunkSize,
38
- chunkOverlap: request.chunkOverlap,
39
- // In a real implementation, you'd convert the file to base64 or use FormData
40
- file: request.file,
41
- };
42
-
32
+ // Use unified /v1/ingest endpoint matching the app
43
33
  return this.httpClient.request<IngestResponse>({
44
34
  method: 'POST',
45
- path: '/v1/ingest/file',
46
- body,
35
+ path: '/v1/ingest',
36
+ body: {
37
+ type: 'pdf',
38
+ projectId: request.projectId,
39
+ metadata: request.metadata || {},
40
+ file: request.file,
41
+ },
47
42
  });
48
43
  }
49
44
 
@@ -68,10 +63,82 @@ export class IngestResource {
68
63
  );
69
64
  }
70
65
 
66
+ // Use unified /v1/ingest endpoint matching the app
71
67
  return this.httpClient.request<IngestResponse>({
72
68
  method: 'POST',
73
- path: '/v1/ingest/text',
74
- body: request,
69
+ path: '/v1/ingest',
70
+ body: {
71
+ type: 'text',
72
+ content: request.text,
73
+ projectId: request.projectId,
74
+ metadata: request.metadata || {},
75
+ },
75
76
  });
76
77
  }
78
+
79
+ /**
80
+ * Ingest content from a URL
81
+ * @param url - URL to ingest from
82
+ * @param projectId - Project ID
83
+ * @param metadata - Optional metadata
84
+ * @returns Ingestion response with job details
85
+ */
86
+ async url(url: string, projectId: string, metadata?: Record<string, any>): Promise<IngestResponse> {
87
+ // Validate request
88
+ if (!url || url.trim().length === 0) {
89
+ throw new ValidationError(
90
+ 'URL cannot be empty',
91
+ [{ field: 'url', message: 'URL is required and cannot be empty' }]
92
+ );
93
+ }
94
+
95
+ if (!projectId || projectId.trim().length === 0) {
96
+ throw new ValidationError(
97
+ 'Project ID is required',
98
+ [{ field: 'projectId', message: 'Project ID is required' }]
99
+ );
100
+ }
101
+
102
+ // Use unified /v1/ingest endpoint matching the app
103
+ return this.httpClient.request<IngestResponse>({
104
+ method: 'POST',
105
+ path: '/v1/ingest',
106
+ body: {
107
+ type: 'url',
108
+ url,
109
+ projectId,
110
+ metadata: metadata || {},
111
+ },
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Get the status of an ingestion job
117
+ * @param jobId - Job ID returned from ingest
118
+ * @param projectId - Project ID
119
+ * @returns Job status information
120
+ */
121
+ async getJob(jobId: string, projectId: string): Promise<any> {
122
+ if (!jobId || jobId.trim().length === 0) {
123
+ throw new ValidationError(
124
+ 'Job ID is required',
125
+ [{ field: 'jobId', message: 'Job ID is required' }]
126
+ );
127
+ }
128
+
129
+ if (!projectId || projectId.trim().length === 0) {
130
+ throw new ValidationError(
131
+ 'Project ID is required',
132
+ [{ field: 'projectId', message: 'Project ID is required' }]
133
+ );
134
+ }
135
+
136
+ const response = await this.httpClient.request<any>({
137
+ method: 'GET',
138
+ path: `/v1/jobs/${jobId}`,
139
+ query: { projectId },
140
+ });
141
+
142
+ return response.data || response;
143
+ }
77
144
  }
@@ -9,9 +9,18 @@ export class SearchResource {
9
9
  constructor(private httpClient: HTTPClient) {}
10
10
 
11
11
  /**
12
- * Search memories
12
+ * Search memories using the unified /v1/search endpoint with hybrid retrieval.
13
+ *
14
+ * This uses the app's full retrieval pipeline with:
15
+ * - Vector similarity search
16
+ * - BM25 keyword search
17
+ * - Recency scoring
18
+ * - Graph connectivity (optional)
19
+ * - Entity expansion (optional)
20
+ * - LLM/Cross-encoder reranking (optional)
21
+ *
13
22
  * @param request - Search request
14
- * @returns Search results
23
+ * @returns Search results with memory pack structure
15
24
  */
16
25
  async search(request: SearchRequest): Promise<SearchResponse> {
17
26
  // Validate request
@@ -29,27 +38,46 @@ export class SearchResource {
29
38
  );
30
39
  }
31
40
 
32
- const query: Record<string, string> = {
33
- q: request.query,
34
- projectId: request.projectId,
41
+ // Build request body matching the app's POST /v1/search endpoint
42
+ const body: any = {
43
+ query: request.query,
44
+ project_id: request.projectId,
45
+ include_text_format: true,
35
46
  };
36
47
 
37
48
  if (request.limit !== undefined) {
38
- query.limit = request.limit.toString();
49
+ body.limit = request.limit;
39
50
  }
40
51
 
41
- if (request.threshold !== undefined) {
42
- query.threshold = request.threshold.toString();
43
- }
44
-
45
- if (request.filter) {
46
- query.filter = JSON.stringify(request.filter);
47
- }
52
+ // Use rerank_strategy to match app's parameter name
53
+ body.rerank_strategy = request.rerankingStrategy || 'cross-encoder';
48
54
 
49
- return this.httpClient.request<SearchResponse>({
50
- method: 'GET',
55
+ // Use POST method to match the app's endpoint
56
+ const response = await this.httpClient.request<any>({
57
+ method: 'POST',
51
58
  path: '/v1/search',
52
- query,
59
+ body,
53
60
  });
61
+
62
+ // Parse response from memory_pack format
63
+ const memoryPack = response.memory_pack || {};
64
+ const results = [];
65
+
66
+ // Extract memories from memory pack structure
67
+ for (const memoryType of ['facts', 'preferences', 'entities', 'sources']) {
68
+ const items = memoryPack[memoryType] || [];
69
+ for (const item of items) {
70
+ results.push({
71
+ memory: item,
72
+ score: item.score || 1.0,
73
+ highlights: item.highlights || [],
74
+ });
75
+ }
76
+ }
77
+
78
+ return {
79
+ results,
80
+ total: results.length,
81
+ };
54
82
  }
55
83
  }
package/src/types.ts CHANGED
@@ -60,12 +60,30 @@ export interface SearchRequest {
60
60
  query: string;
61
61
  /** Project ID to search in */
62
62
  projectId: string;
63
- /** Maximum number of results to return (default: 10) */
63
+ /** Maximum number of results to return (default: 10 - supermemory production default) */
64
64
  limit?: number;
65
65
  /** Filter criteria for search */
66
66
  filter?: Record<string, any>;
67
- /** Minimum relevance score threshold (0-1) */
67
+ /** Minimum relevance score threshold (0-1, default: 0.6 - supermemory production default for broad recall) */
68
68
  threshold?: number;
69
+ /** Enable query rewriting (default: false - adds ~400ms latency) */
70
+ enableQueryRewriting?: boolean;
71
+ /** Enable entity expansion search (default: false) */
72
+ enableEntityExpansion?: boolean;
73
+ /** Enable graph connectivity search (default: false) */
74
+ enableGraphConnectivity?: boolean;
75
+ /** Enable semantic deduplication (default: false) */
76
+ enableSemanticDedup?: boolean;
77
+ /** Reranking strategy: 'none', 'cross-encoder', 'llm' (default: 'none' - adds latency) */
78
+ rerankingStrategy?: 'none' | 'cross-encoder' | 'llm';
79
+ /** Custom fusion weights for multi-method retrieval */
80
+ fusionWeights?: {
81
+ vector?: number;
82
+ bm25?: number;
83
+ recency?: number;
84
+ entity?: number;
85
+ graph?: number;
86
+ };
69
87
  }
70
88
 
71
89
  /**
@@ -78,6 +96,20 @@ export interface SearchResult {
78
96
  score: number;
79
97
  /** Highlighted text snippets */
80
98
  highlights?: string[];
99
+ /** Score breakdown by retrieval method */
100
+ scoreBreakdown?: {
101
+ vectorScore?: number;
102
+ bm25Score?: number;
103
+ recencyScore?: number;
104
+ entityScore?: number;
105
+ graphScore?: number;
106
+ };
107
+ /** Connected memories (if graph enhancement enabled) */
108
+ connections?: Array<{
109
+ memoryId: string;
110
+ connectionType: 'updates' | 'extends' | 'derives' | 'similarity';
111
+ connectionStrength: number;
112
+ }>;
81
113
  }
82
114
 
83
115
  /**
@@ -100,9 +132,9 @@ export interface IngestFileRequest {
100
132
  projectId: string;
101
133
  /** Optional metadata to associate with ingested memories */
102
134
  metadata?: Record<string, any>;
103
- /** Chunk size for splitting the file (default: 1000) */
135
+ /** Chunk size for splitting the file (default: 512 tokens - supermemory production default) */
104
136
  chunkSize?: number;
105
- /** Overlap between chunks (default: 200) */
137
+ /** Overlap between chunks (default: 10% - supermemory production default) */
106
138
  chunkOverlap?: number;
107
139
  }
108
140
 
@@ -116,9 +148,9 @@ export interface IngestTextRequest {
116
148
  projectId: string;
117
149
  /** Optional metadata to associate with ingested memories */
118
150
  metadata?: Record<string, any>;
119
- /** Chunk size for splitting the text (default: 1000) */
151
+ /** Chunk size for splitting the text (default: 512 tokens - supermemory production default) */
120
152
  chunkSize?: number;
121
- /** Overlap between chunks (default: 200) */
153
+ /** Overlap between chunks (default: 10% - supermemory production default) */
122
154
  chunkOverlap?: number;
123
155
  }
124
156
 
@@ -150,11 +182,11 @@ export interface RouterRequest {
150
182
  messages: Message[];
151
183
  /** Project ID for memory context */
152
184
  projectId: string;
153
- /** Model to use (optional) */
185
+ /** Model to use (default: 'gpt-4o-mini' - supermemory production default) */
154
186
  model?: string;
155
- /** Temperature for generation (0-2, default: 1) */
187
+ /** Temperature for generation (0-2, default: 0.7 - supermemory production default) */
156
188
  temperature?: number;
157
- /** Maximum tokens to generate */
189
+ /** Maximum tokens to generate (default: 2000 - supermemory production default) */
158
190
  maxTokens?: number;
159
191
  /** Whether to stream the response */
160
192
  stream?: boolean;