@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/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memorylayerai/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Official Node.js/TypeScript SDK for MemoryLayer",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
19
|
+
"test": "vitest",
|
|
20
|
+
"test:watch": "vitest --watch",
|
|
21
|
+
"lint": "eslint src --ext .ts",
|
|
22
|
+
"typecheck": "tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"memorylayer",
|
|
26
|
+
"memory",
|
|
27
|
+
"ai",
|
|
28
|
+
"llm",
|
|
29
|
+
"sdk",
|
|
30
|
+
"typescript"
|
|
31
|
+
],
|
|
32
|
+
"author": "MemoryLayer",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"zod": "^3.22.4"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^20.11.30",
|
|
39
|
+
"fast-check": "^3.17.1",
|
|
40
|
+
"tsup": "^8.0.2",
|
|
41
|
+
"typescript": "^5.4.3",
|
|
42
|
+
"vitest": "^1.4.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=16.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { HTTPClient, ClientConfig as HTTPClientConfig } from './http-client.js';
|
|
2
|
+
import { ValidationError } from './errors.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for the MemoryLayer client
|
|
6
|
+
*/
|
|
7
|
+
export interface ClientConfig {
|
|
8
|
+
/** API key for authentication (can also be set via MEMORYLAYER_API_KEY env var) */
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
/** Base URL for the API (default: https://api.memorylayer.com) */
|
|
11
|
+
baseURL?: string;
|
|
12
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
15
|
+
maxRetries?: number;
|
|
16
|
+
/** Initial retry delay in milliseconds (default: 1000) */
|
|
17
|
+
retryDelay?: number;
|
|
18
|
+
/** Custom headers to include in all requests */
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
/** Logger for debugging */
|
|
21
|
+
logger?: {
|
|
22
|
+
debug(message: string, ...args: any[]): void;
|
|
23
|
+
info(message: string, ...args: any[]): void;
|
|
24
|
+
warn(message: string, ...args: any[]): void;
|
|
25
|
+
error(message: string, ...args: any[]): void;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Main MemoryLayer SDK client
|
|
31
|
+
*/
|
|
32
|
+
export class MemoryLayerClient {
|
|
33
|
+
private httpClient: HTTPClient;
|
|
34
|
+
private _memories?: any;
|
|
35
|
+
private _search?: any;
|
|
36
|
+
private _ingest?: any;
|
|
37
|
+
private _router?: any;
|
|
38
|
+
|
|
39
|
+
constructor(config: ClientConfig = {}) {
|
|
40
|
+
// Get API key from config or environment variable
|
|
41
|
+
const apiKey = config.apiKey || process.env.MEMORYLAYER_API_KEY;
|
|
42
|
+
|
|
43
|
+
if (!apiKey) {
|
|
44
|
+
throw new ValidationError(
|
|
45
|
+
'API key is required. Provide it via config.apiKey or MEMORYLAYER_API_KEY environment variable.',
|
|
46
|
+
[{ field: 'apiKey', message: 'API key is required' }]
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Validate API key format (basic check)
|
|
51
|
+
if (typeof apiKey !== 'string' || apiKey.trim().length === 0) {
|
|
52
|
+
throw new ValidationError(
|
|
53
|
+
'API key must be a non-empty string',
|
|
54
|
+
[{ field: 'apiKey', message: 'API key must be a non-empty string' }]
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Initialize HTTP client
|
|
59
|
+
const httpConfig: HTTPClientConfig = {
|
|
60
|
+
apiKey,
|
|
61
|
+
baseURL: config.baseURL,
|
|
62
|
+
timeout: config.timeout,
|
|
63
|
+
maxRetries: config.maxRetries,
|
|
64
|
+
retryDelay: config.retryDelay,
|
|
65
|
+
headers: config.headers,
|
|
66
|
+
logger: config.logger,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
this.httpClient = new HTTPClient(httpConfig);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Access memory operations
|
|
74
|
+
*/
|
|
75
|
+
get memories() {
|
|
76
|
+
if (!this._memories) {
|
|
77
|
+
// Lazy load to avoid circular dependencies
|
|
78
|
+
const { MemoriesResource } = require('./resources/memories.js');
|
|
79
|
+
this._memories = new MemoriesResource(this.httpClient);
|
|
80
|
+
}
|
|
81
|
+
return this._memories;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Access search operations
|
|
86
|
+
*/
|
|
87
|
+
get search() {
|
|
88
|
+
if (!this._search) {
|
|
89
|
+
const { SearchResource } = require('./resources/search.js');
|
|
90
|
+
this._search = new SearchResource(this.httpClient);
|
|
91
|
+
}
|
|
92
|
+
return this._search;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Access ingestion operations
|
|
97
|
+
*/
|
|
98
|
+
get ingest() {
|
|
99
|
+
if (!this._ingest) {
|
|
100
|
+
const { IngestResource } = require('./resources/ingest.js');
|
|
101
|
+
this._ingest = new IngestResource(this.httpClient);
|
|
102
|
+
}
|
|
103
|
+
return this._ingest;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Access router operations
|
|
108
|
+
*/
|
|
109
|
+
get router() {
|
|
110
|
+
if (!this._router) {
|
|
111
|
+
const { RouterResource } = require('./resources/router.js');
|
|
112
|
+
this._router = new RouterResource(this.httpClient);
|
|
113
|
+
}
|
|
114
|
+
return this._router;
|
|
115
|
+
}
|
|
116
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all MemoryLayer SDK errors
|
|
3
|
+
*/
|
|
4
|
+
export class MemoryLayerError extends Error {
|
|
5
|
+
constructor(
|
|
6
|
+
message: string,
|
|
7
|
+
public statusCode?: number,
|
|
8
|
+
public requestId?: string,
|
|
9
|
+
public details?: any
|
|
10
|
+
) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'MemoryLayerError';
|
|
13
|
+
Object.setPrototypeOf(this, MemoryLayerError.prototype);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Error thrown when authentication fails (401)
|
|
19
|
+
*/
|
|
20
|
+
export class AuthenticationError extends MemoryLayerError {
|
|
21
|
+
constructor(message: string, requestId?: string) {
|
|
22
|
+
super(message, 401, requestId);
|
|
23
|
+
this.name = 'AuthenticationError';
|
|
24
|
+
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Error thrown when rate limit is exceeded (429)
|
|
30
|
+
*/
|
|
31
|
+
export class RateLimitError extends MemoryLayerError {
|
|
32
|
+
constructor(
|
|
33
|
+
message: string,
|
|
34
|
+
public retryAfter: number,
|
|
35
|
+
requestId?: string
|
|
36
|
+
) {
|
|
37
|
+
super(message, 429, requestId);
|
|
38
|
+
this.name = 'RateLimitError';
|
|
39
|
+
Object.setPrototypeOf(this, RateLimitError.prototype);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validation error detail
|
|
45
|
+
*/
|
|
46
|
+
export interface ValidationErrorDetail {
|
|
47
|
+
field: string;
|
|
48
|
+
message: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Error thrown when request validation fails (400)
|
|
53
|
+
*/
|
|
54
|
+
export class ValidationError extends MemoryLayerError {
|
|
55
|
+
constructor(
|
|
56
|
+
message: string,
|
|
57
|
+
public errors: ValidationErrorDetail[],
|
|
58
|
+
requestId?: string
|
|
59
|
+
) {
|
|
60
|
+
super(message, 400, requestId);
|
|
61
|
+
this.name = 'ValidationError';
|
|
62
|
+
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Error thrown when network request fails
|
|
68
|
+
*/
|
|
69
|
+
export class NetworkError extends MemoryLayerError {
|
|
70
|
+
constructor(message: string, public cause: Error) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.name = 'NetworkError';
|
|
73
|
+
Object.setPrototypeOf(this, NetworkError.prototype);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Error thrown for other API errors
|
|
79
|
+
*/
|
|
80
|
+
export class APIError extends MemoryLayerError {
|
|
81
|
+
constructor(
|
|
82
|
+
message: string,
|
|
83
|
+
statusCode: number,
|
|
84
|
+
requestId?: string,
|
|
85
|
+
details?: any
|
|
86
|
+
) {
|
|
87
|
+
super(message, statusCode, requestId, details);
|
|
88
|
+
this.name = 'APIError';
|
|
89
|
+
Object.setPrototypeOf(this, APIError.prototype);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MemoryLayerError,
|
|
3
|
+
AuthenticationError,
|
|
4
|
+
RateLimitError,
|
|
5
|
+
ValidationError,
|
|
6
|
+
NetworkError,
|
|
7
|
+
APIError,
|
|
8
|
+
} from './errors.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Configuration for the HTTP client
|
|
12
|
+
*/
|
|
13
|
+
export interface ClientConfig {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
baseURL?: string;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
maxRetries?: number;
|
|
18
|
+
retryDelay?: number;
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
logger?: Logger;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Logger interface
|
|
25
|
+
*/
|
|
26
|
+
export interface Logger {
|
|
27
|
+
debug(message: string, ...args: any[]): void;
|
|
28
|
+
info(message: string, ...args: any[]): void;
|
|
29
|
+
warn(message: string, ...args: any[]): void;
|
|
30
|
+
error(message: string, ...args: any[]): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Retry configuration
|
|
35
|
+
*/
|
|
36
|
+
export interface RetryConfig {
|
|
37
|
+
maxRetries: number;
|
|
38
|
+
initialDelay: number;
|
|
39
|
+
maxDelay: number;
|
|
40
|
+
backoffMultiplier: number;
|
|
41
|
+
retryableStatusCodes: number[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Request options
|
|
46
|
+
*/
|
|
47
|
+
export interface RequestOptions {
|
|
48
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
49
|
+
path: string;
|
|
50
|
+
body?: any;
|
|
51
|
+
query?: Record<string, string>;
|
|
52
|
+
headers?: Record<string, string>;
|
|
53
|
+
timeout?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* HTTP client with retry logic and error handling
|
|
58
|
+
*/
|
|
59
|
+
export class HTTPClient {
|
|
60
|
+
private config: ClientConfig;
|
|
61
|
+
private retryConfig: RetryConfig;
|
|
62
|
+
private baseURL: string;
|
|
63
|
+
|
|
64
|
+
constructor(config: ClientConfig, retryConfig?: Partial<RetryConfig>) {
|
|
65
|
+
this.config = config;
|
|
66
|
+
this.baseURL = config.baseURL || 'https://api.memorylayer.com';
|
|
67
|
+
this.retryConfig = {
|
|
68
|
+
maxRetries: config.maxRetries ?? 3,
|
|
69
|
+
initialDelay: config.retryDelay ?? 1000,
|
|
70
|
+
maxDelay: 30000,
|
|
71
|
+
backoffMultiplier: 2,
|
|
72
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
73
|
+
...retryConfig,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Make an HTTP request with retry logic
|
|
79
|
+
*/
|
|
80
|
+
async request<T>(options: RequestOptions): Promise<T> {
|
|
81
|
+
let lastError: Error | null = null;
|
|
82
|
+
|
|
83
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
84
|
+
try {
|
|
85
|
+
return await this.makeRequest<T>(options);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
lastError = error as Error;
|
|
88
|
+
|
|
89
|
+
if (!this.shouldRetry(error as MemoryLayerError, attempt)) {
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const delay = this.getRetryDelay(error as MemoryLayerError, attempt);
|
|
94
|
+
this.config.logger?.debug(`Retrying request after ${delay}ms (attempt ${attempt + 1}/${this.retryConfig.maxRetries})`);
|
|
95
|
+
await this.sleep(delay);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
throw lastError;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Make a streaming request
|
|
104
|
+
*/
|
|
105
|
+
async *stream(options: RequestOptions): AsyncIterable<any> {
|
|
106
|
+
const url = this.buildURL(options.path, options.query);
|
|
107
|
+
const headers = this.buildHeaders(options.headers);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const response = await fetch(url, {
|
|
111
|
+
method: options.method,
|
|
112
|
+
headers,
|
|
113
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
114
|
+
signal: AbortSignal.timeout(options.timeout || this.config.timeout || 60000),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
throw await this.handleErrorResponse(response);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!response.body) {
|
|
122
|
+
throw new NetworkError('Response body is null', new Error('No response body'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const reader = response.body.getReader();
|
|
126
|
+
const decoder = new TextDecoder();
|
|
127
|
+
let buffer = '';
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
while (true) {
|
|
131
|
+
const { done, value } = await reader.read();
|
|
132
|
+
|
|
133
|
+
if (done) {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
buffer += decoder.decode(value, { stream: true });
|
|
138
|
+
const lines = buffer.split('\n');
|
|
139
|
+
buffer = lines.pop() || '';
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
if (line.trim() === '') continue;
|
|
143
|
+
if (line.startsWith('data: ')) {
|
|
144
|
+
const data = line.slice(6);
|
|
145
|
+
if (data === '[DONE]') continue;
|
|
146
|
+
try {
|
|
147
|
+
yield JSON.parse(data);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
this.config.logger?.warn('Failed to parse streaming chunk:', data);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} finally {
|
|
155
|
+
reader.releaseLock();
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (error instanceof MemoryLayerError) {
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
throw new NetworkError('Streaming request failed', error as Error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Make the actual HTTP request
|
|
167
|
+
*/
|
|
168
|
+
private async makeRequest<T>(options: RequestOptions): Promise<T> {
|
|
169
|
+
const url = this.buildURL(options.path, options.query);
|
|
170
|
+
const headers = this.buildHeaders(options.headers);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch(url, {
|
|
174
|
+
method: options.method,
|
|
175
|
+
headers,
|
|
176
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
177
|
+
signal: AbortSignal.timeout(options.timeout || this.config.timeout || 30000),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
throw await this.handleErrorResponse(response);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return await response.json();
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error instanceof MemoryLayerError) {
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
throw new NetworkError('Request failed', error as Error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Build the full URL with query parameters
|
|
195
|
+
*/
|
|
196
|
+
private buildURL(path: string, query?: Record<string, string>): string {
|
|
197
|
+
const url = new URL(path, this.baseURL);
|
|
198
|
+
if (query) {
|
|
199
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
200
|
+
url.searchParams.append(key, value);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return url.toString();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Build request headers
|
|
208
|
+
*/
|
|
209
|
+
private buildHeaders(additionalHeaders?: Record<string, string>): Record<string, string> {
|
|
210
|
+
return {
|
|
211
|
+
'Content-Type': 'application/json',
|
|
212
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
213
|
+
'X-SDK-Version': '0.1.0',
|
|
214
|
+
'X-API-Version': 'v1',
|
|
215
|
+
...this.config.headers,
|
|
216
|
+
...additionalHeaders,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handle error responses from the API
|
|
222
|
+
*/
|
|
223
|
+
private async handleErrorResponse(response: Response): Promise<MemoryLayerError> {
|
|
224
|
+
const requestId = response.headers.get('x-request-id') || undefined;
|
|
225
|
+
let errorData: any;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
errorData = await response.json();
|
|
229
|
+
} catch {
|
|
230
|
+
errorData = { message: response.statusText };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const message = errorData.error?.message || errorData.message || 'Unknown error';
|
|
234
|
+
const details = errorData.error?.details || errorData.details;
|
|
235
|
+
|
|
236
|
+
switch (response.status) {
|
|
237
|
+
case 401:
|
|
238
|
+
return new AuthenticationError(message, requestId);
|
|
239
|
+
case 429:
|
|
240
|
+
const retryAfter = parseInt(response.headers.get('retry-after') || '60', 10);
|
|
241
|
+
return new RateLimitError(message, retryAfter, requestId);
|
|
242
|
+
case 400:
|
|
243
|
+
const errors = errorData.error?.errors || [];
|
|
244
|
+
return new ValidationError(message, errors, requestId);
|
|
245
|
+
default:
|
|
246
|
+
return new APIError(message, response.status, requestId, details);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Determine if an error should be retried
|
|
252
|
+
*/
|
|
253
|
+
private shouldRetry(error: MemoryLayerError, attempt: number): boolean {
|
|
254
|
+
if (attempt >= this.retryConfig.maxRetries) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Retry network errors
|
|
259
|
+
if (error instanceof NetworkError) {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Retry rate limit errors
|
|
264
|
+
if (error instanceof RateLimitError) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Retry specific status codes
|
|
269
|
+
if (error.statusCode && this.retryConfig.retryableStatusCodes.includes(error.statusCode)) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Don't retry client errors (4xx except 429)
|
|
274
|
+
if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Calculate retry delay with exponential backoff
|
|
283
|
+
*/
|
|
284
|
+
private getRetryDelay(error: MemoryLayerError, attempt: number): number {
|
|
285
|
+
// Respect Retry-After header for rate limit errors
|
|
286
|
+
if (error instanceof RateLimitError) {
|
|
287
|
+
return error.retryAfter * 1000;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Exponential backoff with jitter
|
|
291
|
+
const exponentialDelay = this.retryConfig.initialDelay * Math.pow(this.retryConfig.backoffMultiplier, attempt);
|
|
292
|
+
const jitter = Math.random() * 0.1 * exponentialDelay; // 10% jitter
|
|
293
|
+
const delay = Math.min(exponentialDelay + jitter, this.retryConfig.maxDelay);
|
|
294
|
+
|
|
295
|
+
return delay;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Sleep for a specified duration
|
|
300
|
+
*/
|
|
301
|
+
private sleep(ms: number): Promise<void> {
|
|
302
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
303
|
+
}
|
|
304
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryLayer Node.js/TypeScript SDK
|
|
3
|
+
*
|
|
4
|
+
* Official SDK for integrating MemoryLayer into your applications.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Main client
|
|
10
|
+
export { MemoryLayerClient } from './client.js';
|
|
11
|
+
export type { ClientConfig } from './client.js';
|
|
12
|
+
|
|
13
|
+
// Error classes
|
|
14
|
+
export {
|
|
15
|
+
MemoryLayerError,
|
|
16
|
+
AuthenticationError,
|
|
17
|
+
RateLimitError,
|
|
18
|
+
ValidationError,
|
|
19
|
+
NetworkError,
|
|
20
|
+
APIError,
|
|
21
|
+
} from './errors.js';
|
|
22
|
+
export type { ValidationErrorDetail } from './errors.js';
|
|
23
|
+
|
|
24
|
+
// Type definitions
|
|
25
|
+
export type {
|
|
26
|
+
Memory,
|
|
27
|
+
CreateMemoryRequest,
|
|
28
|
+
UpdateMemoryRequest,
|
|
29
|
+
ListMemoriesRequest,
|
|
30
|
+
SearchRequest,
|
|
31
|
+
SearchResult,
|
|
32
|
+
SearchResponse,
|
|
33
|
+
IngestFileRequest,
|
|
34
|
+
IngestTextRequest,
|
|
35
|
+
IngestResponse,
|
|
36
|
+
Message,
|
|
37
|
+
RouterRequest,
|
|
38
|
+
RouterResponse,
|
|
39
|
+
Usage,
|
|
40
|
+
Choice,
|
|
41
|
+
StreamChunk,
|
|
42
|
+
StreamChoice,
|
|
43
|
+
StreamDelta,
|
|
44
|
+
} from './types.js';
|
|
45
|
+
|
|
46
|
+
// Resources (exported for advanced use cases)
|
|
47
|
+
export { MemoriesResource } from './resources/memories.js';
|
|
48
|
+
export { SearchResource } from './resources/search.js';
|
|
49
|
+
export { IngestResource } from './resources/ingest.js';
|
|
50
|
+
export { RouterResource } from './resources/router.js';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { HTTPClient } from '../http-client.js';
|
|
2
|
+
import { IngestFileRequest, IngestTextRequest, IngestResponse } from '../types.js';
|
|
3
|
+
import { ValidationError } from '../errors.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resource for ingestion operations
|
|
7
|
+
*/
|
|
8
|
+
export class IngestResource {
|
|
9
|
+
constructor(private httpClient: HTTPClient) {}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Ingest a file
|
|
13
|
+
* @param request - File ingestion request
|
|
14
|
+
* @returns Ingestion response with created memory IDs
|
|
15
|
+
*/
|
|
16
|
+
async file(request: IngestFileRequest): Promise<IngestResponse> {
|
|
17
|
+
// Validate request
|
|
18
|
+
if (!request.file) {
|
|
19
|
+
throw new ValidationError(
|
|
20
|
+
'File is required',
|
|
21
|
+
[{ field: 'file', message: 'File is required' }]
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!request.projectId || request.projectId.trim().length === 0) {
|
|
26
|
+
throw new ValidationError(
|
|
27
|
+
'Project ID is required',
|
|
28
|
+
[{ field: 'projectId', message: 'Project ID is required' }]
|
|
29
|
+
);
|
|
30
|
+
}
|
|
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
|
+
|
|
43
|
+
return this.httpClient.request<IngestResponse>({
|
|
44
|
+
method: 'POST',
|
|
45
|
+
path: '/v1/ingest/file',
|
|
46
|
+
body,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Ingest text
|
|
52
|
+
* @param request - Text ingestion request
|
|
53
|
+
* @returns Ingestion response with created memory IDs
|
|
54
|
+
*/
|
|
55
|
+
async text(request: IngestTextRequest): Promise<IngestResponse> {
|
|
56
|
+
// Validate request
|
|
57
|
+
if (!request.text || request.text.trim().length === 0) {
|
|
58
|
+
throw new ValidationError(
|
|
59
|
+
'Text cannot be empty',
|
|
60
|
+
[{ field: 'text', message: 'Text is required and cannot be empty' }]
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!request.projectId || request.projectId.trim().length === 0) {
|
|
65
|
+
throw new ValidationError(
|
|
66
|
+
'Project ID is required',
|
|
67
|
+
[{ field: 'projectId', message: 'Project ID is required' }]
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return this.httpClient.request<IngestResponse>({
|
|
72
|
+
method: 'POST',
|
|
73
|
+
path: '/v1/ingest/text',
|
|
74
|
+
body: request,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|