@memorylayerai/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/index.cjs +728 -0
- package/dist/index.d.cts +492 -0
- package/dist/index.d.ts +492 -0
- package/dist/index.js +709 -0
- package/package.json +47 -0
- package/src/client.ts +116 -0
- package/src/errors.ts +91 -0
- package/src/http-client.ts +304 -0
- package/src/index.ts +50 -0
- package/src/resources/ingest.ts +77 -0
- package/src/resources/memories.ts +144 -0
- package/src/resources/router.ts +81 -0
- package/src/resources/search.ts +55 -0
- package/src/types.ts +225 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +13 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __esm = (fn, res) => function __init() {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
};
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/errors.ts
|
|
23
|
+
var MemoryLayerError, AuthenticationError, RateLimitError, ValidationError, NetworkError, APIError;
|
|
24
|
+
var init_errors = __esm({
|
|
25
|
+
"src/errors.ts"() {
|
|
26
|
+
"use strict";
|
|
27
|
+
MemoryLayerError = class _MemoryLayerError extends Error {
|
|
28
|
+
constructor(message, statusCode, requestId, details) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.statusCode = statusCode;
|
|
31
|
+
this.requestId = requestId;
|
|
32
|
+
this.details = details;
|
|
33
|
+
this.name = "MemoryLayerError";
|
|
34
|
+
Object.setPrototypeOf(this, _MemoryLayerError.prototype);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
AuthenticationError = class _AuthenticationError extends MemoryLayerError {
|
|
38
|
+
constructor(message, requestId) {
|
|
39
|
+
super(message, 401, requestId);
|
|
40
|
+
this.name = "AuthenticationError";
|
|
41
|
+
Object.setPrototypeOf(this, _AuthenticationError.prototype);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
RateLimitError = class _RateLimitError extends MemoryLayerError {
|
|
45
|
+
constructor(message, retryAfter, requestId) {
|
|
46
|
+
super(message, 429, requestId);
|
|
47
|
+
this.retryAfter = retryAfter;
|
|
48
|
+
this.name = "RateLimitError";
|
|
49
|
+
Object.setPrototypeOf(this, _RateLimitError.prototype);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
ValidationError = class _ValidationError extends MemoryLayerError {
|
|
53
|
+
constructor(message, errors, requestId) {
|
|
54
|
+
super(message, 400, requestId);
|
|
55
|
+
this.errors = errors;
|
|
56
|
+
this.name = "ValidationError";
|
|
57
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
NetworkError = class _NetworkError extends MemoryLayerError {
|
|
61
|
+
constructor(message, cause) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.cause = cause;
|
|
64
|
+
this.name = "NetworkError";
|
|
65
|
+
Object.setPrototypeOf(this, _NetworkError.prototype);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
APIError = class _APIError extends MemoryLayerError {
|
|
69
|
+
constructor(message, statusCode, requestId, details) {
|
|
70
|
+
super(message, statusCode, requestId, details);
|
|
71
|
+
this.name = "APIError";
|
|
72
|
+
Object.setPrototypeOf(this, _APIError.prototype);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// src/resources/memories.ts
|
|
79
|
+
var memories_exports = {};
|
|
80
|
+
__export(memories_exports, {
|
|
81
|
+
MemoriesResource: () => MemoriesResource
|
|
82
|
+
});
|
|
83
|
+
var MemoriesResource;
|
|
84
|
+
var init_memories = __esm({
|
|
85
|
+
"src/resources/memories.ts"() {
|
|
86
|
+
"use strict";
|
|
87
|
+
init_errors();
|
|
88
|
+
MemoriesResource = class {
|
|
89
|
+
constructor(httpClient) {
|
|
90
|
+
this.httpClient = httpClient;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Add a new memory
|
|
94
|
+
* @param request - Memory creation request
|
|
95
|
+
* @returns The created memory
|
|
96
|
+
*/
|
|
97
|
+
async add(request) {
|
|
98
|
+
if (!request.content || request.content.trim().length === 0) {
|
|
99
|
+
throw new ValidationError(
|
|
100
|
+
"Memory content cannot be empty",
|
|
101
|
+
[{ field: "content", message: "Content is required and cannot be empty" }]
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (!request.projectId || request.projectId.trim().length === 0) {
|
|
105
|
+
throw new ValidationError(
|
|
106
|
+
"Project ID is required",
|
|
107
|
+
[{ field: "projectId", message: "Project ID is required" }]
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return this.httpClient.request({
|
|
111
|
+
method: "POST",
|
|
112
|
+
path: "/v1/memories",
|
|
113
|
+
body: request
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get a memory by ID
|
|
118
|
+
* @param id - Memory ID
|
|
119
|
+
* @returns The memory
|
|
120
|
+
*/
|
|
121
|
+
async get(id) {
|
|
122
|
+
if (!id || id.trim().length === 0) {
|
|
123
|
+
throw new ValidationError(
|
|
124
|
+
"Memory ID is required",
|
|
125
|
+
[{ field: "id", message: "Memory ID is required" }]
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return this.httpClient.request({
|
|
129
|
+
method: "GET",
|
|
130
|
+
path: `/v1/memories/${id}`
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Update a memory
|
|
135
|
+
* @param id - Memory ID
|
|
136
|
+
* @param request - Update request
|
|
137
|
+
* @returns The updated memory
|
|
138
|
+
*/
|
|
139
|
+
async update(id, request) {
|
|
140
|
+
if (!id || id.trim().length === 0) {
|
|
141
|
+
throw new ValidationError(
|
|
142
|
+
"Memory ID is required",
|
|
143
|
+
[{ field: "id", message: "Memory ID is required" }]
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (!request.content && !request.metadata) {
|
|
147
|
+
throw new ValidationError(
|
|
148
|
+
"At least one of content or metadata must be provided",
|
|
149
|
+
[{ field: "request", message: "Nothing to update" }]
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return this.httpClient.request({
|
|
153
|
+
method: "PATCH",
|
|
154
|
+
path: `/v1/memories/${id}`,
|
|
155
|
+
body: request
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Delete a memory
|
|
160
|
+
* @param id - Memory ID
|
|
161
|
+
*/
|
|
162
|
+
async delete(id) {
|
|
163
|
+
if (!id || id.trim().length === 0) {
|
|
164
|
+
throw new ValidationError(
|
|
165
|
+
"Memory ID is required",
|
|
166
|
+
[{ field: "id", message: "Memory ID is required" }]
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
await this.httpClient.request({
|
|
170
|
+
method: "DELETE",
|
|
171
|
+
path: `/v1/memories/${id}`
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* List memories
|
|
176
|
+
* @param request - List request with filters
|
|
177
|
+
* @returns Array of memories
|
|
178
|
+
*/
|
|
179
|
+
async list(request) {
|
|
180
|
+
if (!request.projectId || request.projectId.trim().length === 0) {
|
|
181
|
+
throw new ValidationError(
|
|
182
|
+
"Project ID is required",
|
|
183
|
+
[{ field: "projectId", message: "Project ID is required" }]
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const query = {
|
|
187
|
+
projectId: request.projectId
|
|
188
|
+
};
|
|
189
|
+
if (request.limit !== void 0) {
|
|
190
|
+
query.limit = request.limit.toString();
|
|
191
|
+
}
|
|
192
|
+
if (request.offset !== void 0) {
|
|
193
|
+
query.offset = request.offset.toString();
|
|
194
|
+
}
|
|
195
|
+
if (request.filter) {
|
|
196
|
+
query.filter = JSON.stringify(request.filter);
|
|
197
|
+
}
|
|
198
|
+
return this.httpClient.request({
|
|
199
|
+
method: "GET",
|
|
200
|
+
path: "/v1/memories",
|
|
201
|
+
query
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// src/resources/search.ts
|
|
209
|
+
var search_exports = {};
|
|
210
|
+
__export(search_exports, {
|
|
211
|
+
SearchResource: () => SearchResource
|
|
212
|
+
});
|
|
213
|
+
var SearchResource;
|
|
214
|
+
var init_search = __esm({
|
|
215
|
+
"src/resources/search.ts"() {
|
|
216
|
+
"use strict";
|
|
217
|
+
init_errors();
|
|
218
|
+
SearchResource = class {
|
|
219
|
+
constructor(httpClient) {
|
|
220
|
+
this.httpClient = httpClient;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Search memories
|
|
224
|
+
* @param request - Search request
|
|
225
|
+
* @returns Search results
|
|
226
|
+
*/
|
|
227
|
+
async search(request) {
|
|
228
|
+
if (!request.query || request.query.trim().length === 0) {
|
|
229
|
+
throw new ValidationError(
|
|
230
|
+
"Search query cannot be empty",
|
|
231
|
+
[{ field: "query", message: "Query is required and cannot be empty" }]
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (!request.projectId || request.projectId.trim().length === 0) {
|
|
235
|
+
throw new ValidationError(
|
|
236
|
+
"Project ID is required",
|
|
237
|
+
[{ field: "projectId", message: "Project ID is required" }]
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const query = {
|
|
241
|
+
q: request.query,
|
|
242
|
+
projectId: request.projectId
|
|
243
|
+
};
|
|
244
|
+
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);
|
|
252
|
+
}
|
|
253
|
+
return this.httpClient.request({
|
|
254
|
+
method: "GET",
|
|
255
|
+
path: "/v1/search",
|
|
256
|
+
query
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// src/resources/ingest.ts
|
|
264
|
+
var ingest_exports = {};
|
|
265
|
+
__export(ingest_exports, {
|
|
266
|
+
IngestResource: () => IngestResource
|
|
267
|
+
});
|
|
268
|
+
var IngestResource;
|
|
269
|
+
var init_ingest = __esm({
|
|
270
|
+
"src/resources/ingest.ts"() {
|
|
271
|
+
"use strict";
|
|
272
|
+
init_errors();
|
|
273
|
+
IngestResource = class {
|
|
274
|
+
constructor(httpClient) {
|
|
275
|
+
this.httpClient = httpClient;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Ingest a file
|
|
279
|
+
* @param request - File ingestion request
|
|
280
|
+
* @returns Ingestion response with created memory IDs
|
|
281
|
+
*/
|
|
282
|
+
async file(request) {
|
|
283
|
+
if (!request.file) {
|
|
284
|
+
throw new ValidationError(
|
|
285
|
+
"File is required",
|
|
286
|
+
[{ field: "file", message: "File is required" }]
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (!request.projectId || request.projectId.trim().length === 0) {
|
|
290
|
+
throw new ValidationError(
|
|
291
|
+
"Project ID is required",
|
|
292
|
+
[{ field: "projectId", message: "Project ID is required" }]
|
|
293
|
+
);
|
|
294
|
+
}
|
|
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
|
+
return this.httpClient.request({
|
|
304
|
+
method: "POST",
|
|
305
|
+
path: "/v1/ingest/file",
|
|
306
|
+
body
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Ingest text
|
|
311
|
+
* @param request - Text ingestion request
|
|
312
|
+
* @returns Ingestion response with created memory IDs
|
|
313
|
+
*/
|
|
314
|
+
async text(request) {
|
|
315
|
+
if (!request.text || request.text.trim().length === 0) {
|
|
316
|
+
throw new ValidationError(
|
|
317
|
+
"Text cannot be empty",
|
|
318
|
+
[{ field: "text", message: "Text is required and cannot be empty" }]
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
if (!request.projectId || request.projectId.trim().length === 0) {
|
|
322
|
+
throw new ValidationError(
|
|
323
|
+
"Project ID is required",
|
|
324
|
+
[{ field: "projectId", message: "Project ID is required" }]
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return this.httpClient.request({
|
|
328
|
+
method: "POST",
|
|
329
|
+
path: "/v1/ingest/text",
|
|
330
|
+
body: request
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// src/resources/router.ts
|
|
338
|
+
var router_exports = {};
|
|
339
|
+
__export(router_exports, {
|
|
340
|
+
RouterResource: () => RouterResource
|
|
341
|
+
});
|
|
342
|
+
var RouterResource;
|
|
343
|
+
var init_router = __esm({
|
|
344
|
+
"src/resources/router.ts"() {
|
|
345
|
+
"use strict";
|
|
346
|
+
init_errors();
|
|
347
|
+
RouterResource = class {
|
|
348
|
+
constructor(httpClient) {
|
|
349
|
+
this.httpClient = httpClient;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Complete a router request (non-streaming)
|
|
353
|
+
* @param request - Router request
|
|
354
|
+
* @returns Router response
|
|
355
|
+
*/
|
|
356
|
+
async complete(request) {
|
|
357
|
+
if (!request.messages || request.messages.length === 0) {
|
|
358
|
+
throw new ValidationError(
|
|
359
|
+
"Messages array cannot be empty",
|
|
360
|
+
[{ field: "messages", message: "At least one message is required" }]
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
if (!request.projectId || request.projectId.trim().length === 0) {
|
|
364
|
+
throw new ValidationError(
|
|
365
|
+
"Project ID is required",
|
|
366
|
+
[{ field: "projectId", message: "Project ID is required" }]
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
return this.httpClient.request({
|
|
370
|
+
method: "POST",
|
|
371
|
+
path: "/v1/router/complete",
|
|
372
|
+
body: {
|
|
373
|
+
...request,
|
|
374
|
+
stream: false
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Stream a router request
|
|
380
|
+
* @param request - Router request
|
|
381
|
+
* @returns Async iterable of stream chunks
|
|
382
|
+
*/
|
|
383
|
+
async *stream(request) {
|
|
384
|
+
if (!request.messages || request.messages.length === 0) {
|
|
385
|
+
throw new ValidationError(
|
|
386
|
+
"Messages array cannot be empty",
|
|
387
|
+
[{ field: "messages", message: "At least one message is required" }]
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
if (!request.projectId || request.projectId.trim().length === 0) {
|
|
391
|
+
throw new ValidationError(
|
|
392
|
+
"Project ID is required",
|
|
393
|
+
[{ field: "projectId", message: "Project ID is required" }]
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
const stream = this.httpClient.stream({
|
|
397
|
+
method: "POST",
|
|
398
|
+
path: "/v1/router/complete",
|
|
399
|
+
body: {
|
|
400
|
+
...request,
|
|
401
|
+
stream: true
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
try {
|
|
405
|
+
for await (const chunk of stream) {
|
|
406
|
+
yield chunk;
|
|
407
|
+
}
|
|
408
|
+
} catch (error) {
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// src/http-client.ts
|
|
417
|
+
init_errors();
|
|
418
|
+
var HTTPClient = class {
|
|
419
|
+
constructor(config, retryConfig) {
|
|
420
|
+
this.config = config;
|
|
421
|
+
this.baseURL = config.baseURL || "https://api.memorylayer.com";
|
|
422
|
+
this.retryConfig = {
|
|
423
|
+
maxRetries: config.maxRetries ?? 3,
|
|
424
|
+
initialDelay: config.retryDelay ?? 1e3,
|
|
425
|
+
maxDelay: 3e4,
|
|
426
|
+
backoffMultiplier: 2,
|
|
427
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
428
|
+
...retryConfig
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Make an HTTP request with retry logic
|
|
433
|
+
*/
|
|
434
|
+
async request(options) {
|
|
435
|
+
let lastError = null;
|
|
436
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
437
|
+
try {
|
|
438
|
+
return await this.makeRequest(options);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
lastError = error;
|
|
441
|
+
if (!this.shouldRetry(error, attempt)) {
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
const delay = this.getRetryDelay(error, attempt);
|
|
445
|
+
this.config.logger?.debug(`Retrying request after ${delay}ms (attempt ${attempt + 1}/${this.retryConfig.maxRetries})`);
|
|
446
|
+
await this.sleep(delay);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
throw lastError;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Make a streaming request
|
|
453
|
+
*/
|
|
454
|
+
async *stream(options) {
|
|
455
|
+
const url = this.buildURL(options.path, options.query);
|
|
456
|
+
const headers = this.buildHeaders(options.headers);
|
|
457
|
+
try {
|
|
458
|
+
const response = await fetch(url, {
|
|
459
|
+
method: options.method,
|
|
460
|
+
headers,
|
|
461
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
462
|
+
signal: AbortSignal.timeout(options.timeout || this.config.timeout || 6e4)
|
|
463
|
+
});
|
|
464
|
+
if (!response.ok) {
|
|
465
|
+
throw await this.handleErrorResponse(response);
|
|
466
|
+
}
|
|
467
|
+
if (!response.body) {
|
|
468
|
+
throw new NetworkError("Response body is null", new Error("No response body"));
|
|
469
|
+
}
|
|
470
|
+
const reader = response.body.getReader();
|
|
471
|
+
const decoder = new TextDecoder();
|
|
472
|
+
let buffer = "";
|
|
473
|
+
try {
|
|
474
|
+
while (true) {
|
|
475
|
+
const { done, value } = await reader.read();
|
|
476
|
+
if (done) {
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
buffer += decoder.decode(value, { stream: true });
|
|
480
|
+
const lines = buffer.split("\n");
|
|
481
|
+
buffer = lines.pop() || "";
|
|
482
|
+
for (const line of lines) {
|
|
483
|
+
if (line.trim() === "") continue;
|
|
484
|
+
if (line.startsWith("data: ")) {
|
|
485
|
+
const data = line.slice(6);
|
|
486
|
+
if (data === "[DONE]") continue;
|
|
487
|
+
try {
|
|
488
|
+
yield JSON.parse(data);
|
|
489
|
+
} catch (e) {
|
|
490
|
+
this.config.logger?.warn("Failed to parse streaming chunk:", data);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} finally {
|
|
496
|
+
reader.releaseLock();
|
|
497
|
+
}
|
|
498
|
+
} catch (error) {
|
|
499
|
+
if (error instanceof MemoryLayerError) {
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
502
|
+
throw new NetworkError("Streaming request failed", error);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Make the actual HTTP request
|
|
507
|
+
*/
|
|
508
|
+
async makeRequest(options) {
|
|
509
|
+
const url = this.buildURL(options.path, options.query);
|
|
510
|
+
const headers = this.buildHeaders(options.headers);
|
|
511
|
+
try {
|
|
512
|
+
const response = await fetch(url, {
|
|
513
|
+
method: options.method,
|
|
514
|
+
headers,
|
|
515
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
516
|
+
signal: AbortSignal.timeout(options.timeout || this.config.timeout || 3e4)
|
|
517
|
+
});
|
|
518
|
+
if (!response.ok) {
|
|
519
|
+
throw await this.handleErrorResponse(response);
|
|
520
|
+
}
|
|
521
|
+
return await response.json();
|
|
522
|
+
} catch (error) {
|
|
523
|
+
if (error instanceof MemoryLayerError) {
|
|
524
|
+
throw error;
|
|
525
|
+
}
|
|
526
|
+
throw new NetworkError("Request failed", error);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Build the full URL with query parameters
|
|
531
|
+
*/
|
|
532
|
+
buildURL(path, query) {
|
|
533
|
+
const url = new URL(path, this.baseURL);
|
|
534
|
+
if (query) {
|
|
535
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
536
|
+
url.searchParams.append(key, value);
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
return url.toString();
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Build request headers
|
|
543
|
+
*/
|
|
544
|
+
buildHeaders(additionalHeaders) {
|
|
545
|
+
return {
|
|
546
|
+
"Content-Type": "application/json",
|
|
547
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
548
|
+
"X-SDK-Version": "0.1.0",
|
|
549
|
+
"X-API-Version": "v1",
|
|
550
|
+
...this.config.headers,
|
|
551
|
+
...additionalHeaders
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Handle error responses from the API
|
|
556
|
+
*/
|
|
557
|
+
async handleErrorResponse(response) {
|
|
558
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
559
|
+
let errorData;
|
|
560
|
+
try {
|
|
561
|
+
errorData = await response.json();
|
|
562
|
+
} catch {
|
|
563
|
+
errorData = { message: response.statusText };
|
|
564
|
+
}
|
|
565
|
+
const message = errorData.error?.message || errorData.message || "Unknown error";
|
|
566
|
+
const details = errorData.error?.details || errorData.details;
|
|
567
|
+
switch (response.status) {
|
|
568
|
+
case 401:
|
|
569
|
+
return new AuthenticationError(message, requestId);
|
|
570
|
+
case 429:
|
|
571
|
+
const retryAfter = parseInt(response.headers.get("retry-after") || "60", 10);
|
|
572
|
+
return new RateLimitError(message, retryAfter, requestId);
|
|
573
|
+
case 400:
|
|
574
|
+
const errors = errorData.error?.errors || [];
|
|
575
|
+
return new ValidationError(message, errors, requestId);
|
|
576
|
+
default:
|
|
577
|
+
return new APIError(message, response.status, requestId, details);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Determine if an error should be retried
|
|
582
|
+
*/
|
|
583
|
+
shouldRetry(error, attempt) {
|
|
584
|
+
if (attempt >= this.retryConfig.maxRetries) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
if (error instanceof NetworkError) {
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
if (error instanceof RateLimitError) {
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
if (error.statusCode && this.retryConfig.retryableStatusCodes.includes(error.statusCode)) {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Calculate retry delay with exponential backoff
|
|
603
|
+
*/
|
|
604
|
+
getRetryDelay(error, attempt) {
|
|
605
|
+
if (error instanceof RateLimitError) {
|
|
606
|
+
return error.retryAfter * 1e3;
|
|
607
|
+
}
|
|
608
|
+
const exponentialDelay = this.retryConfig.initialDelay * Math.pow(this.retryConfig.backoffMultiplier, attempt);
|
|
609
|
+
const jitter = Math.random() * 0.1 * exponentialDelay;
|
|
610
|
+
const delay = Math.min(exponentialDelay + jitter, this.retryConfig.maxDelay);
|
|
611
|
+
return delay;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Sleep for a specified duration
|
|
615
|
+
*/
|
|
616
|
+
sleep(ms) {
|
|
617
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// src/client.ts
|
|
622
|
+
init_errors();
|
|
623
|
+
var MemoryLayerClient = class {
|
|
624
|
+
constructor(config = {}) {
|
|
625
|
+
const apiKey = config.apiKey || process.env.MEMORYLAYER_API_KEY;
|
|
626
|
+
if (!apiKey) {
|
|
627
|
+
throw new ValidationError(
|
|
628
|
+
"API key is required. Provide it via config.apiKey or MEMORYLAYER_API_KEY environment variable.",
|
|
629
|
+
[{ field: "apiKey", message: "API key is required" }]
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
if (typeof apiKey !== "string" || apiKey.trim().length === 0) {
|
|
633
|
+
throw new ValidationError(
|
|
634
|
+
"API key must be a non-empty string",
|
|
635
|
+
[{ field: "apiKey", message: "API key must be a non-empty string" }]
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
const httpConfig = {
|
|
639
|
+
apiKey,
|
|
640
|
+
baseURL: config.baseURL,
|
|
641
|
+
timeout: config.timeout,
|
|
642
|
+
maxRetries: config.maxRetries,
|
|
643
|
+
retryDelay: config.retryDelay,
|
|
644
|
+
headers: config.headers,
|
|
645
|
+
logger: config.logger
|
|
646
|
+
};
|
|
647
|
+
this.httpClient = new HTTPClient(httpConfig);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Access memory operations
|
|
651
|
+
*/
|
|
652
|
+
get memories() {
|
|
653
|
+
if (!this._memories) {
|
|
654
|
+
const { MemoriesResource: MemoriesResource2 } = (init_memories(), __toCommonJS(memories_exports));
|
|
655
|
+
this._memories = new MemoriesResource2(this.httpClient);
|
|
656
|
+
}
|
|
657
|
+
return this._memories;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Access search operations
|
|
661
|
+
*/
|
|
662
|
+
get search() {
|
|
663
|
+
if (!this._search) {
|
|
664
|
+
const { SearchResource: SearchResource2 } = (init_search(), __toCommonJS(search_exports));
|
|
665
|
+
this._search = new SearchResource2(this.httpClient);
|
|
666
|
+
}
|
|
667
|
+
return this._search;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Access ingestion operations
|
|
671
|
+
*/
|
|
672
|
+
get ingest() {
|
|
673
|
+
if (!this._ingest) {
|
|
674
|
+
const { IngestResource: IngestResource2 } = (init_ingest(), __toCommonJS(ingest_exports));
|
|
675
|
+
this._ingest = new IngestResource2(this.httpClient);
|
|
676
|
+
}
|
|
677
|
+
return this._ingest;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Access router operations
|
|
681
|
+
*/
|
|
682
|
+
get router() {
|
|
683
|
+
if (!this._router) {
|
|
684
|
+
const { RouterResource: RouterResource2 } = (init_router(), __toCommonJS(router_exports));
|
|
685
|
+
this._router = new RouterResource2(this.httpClient);
|
|
686
|
+
}
|
|
687
|
+
return this._router;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// src/index.ts
|
|
692
|
+
init_errors();
|
|
693
|
+
init_memories();
|
|
694
|
+
init_search();
|
|
695
|
+
init_ingest();
|
|
696
|
+
init_router();
|
|
697
|
+
export {
|
|
698
|
+
APIError,
|
|
699
|
+
AuthenticationError,
|
|
700
|
+
IngestResource,
|
|
701
|
+
MemoriesResource,
|
|
702
|
+
MemoryLayerClient,
|
|
703
|
+
MemoryLayerError,
|
|
704
|
+
NetworkError,
|
|
705
|
+
RateLimitError,
|
|
706
|
+
RouterResource,
|
|
707
|
+
SearchResource,
|
|
708
|
+
ValidationError
|
|
709
|
+
};
|