@scitrera/memorylayer-sdk 0.0.5 → 0.2.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 +177 -0
- package/README.md +22 -0
- package/dist/client.d.ts +316 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +896 -67
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/knowledgebase.d.ts +41 -0
- package/dist/knowledgebase.d.ts.map +1 -0
- package/dist/knowledgebase.js +86 -0
- package/dist/knowledgebase.js.map +1 -0
- package/dist/mcp_servers.d.ts +94 -0
- package/dist/mcp_servers.d.ts.map +1 -0
- package/dist/mcp_servers.js +132 -0
- package/dist/mcp_servers.js.map +1 -0
- package/dist/rpg.d.ts +178 -0
- package/dist/rpg.d.ts.map +1 -0
- package/dist/rpg.js +126 -0
- package/dist/rpg.js.map +1 -0
- package/dist/skills.d.ts +194 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +465 -0
- package/dist/skills.js.map +1 -0
- package/dist/types.d.ts +630 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +27 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +43 -0
- package/dist/utils.js.map +1 -1
- package/package.json +11 -3
- package/vitest.config.ts +0 -12
package/dist/client.js
CHANGED
|
@@ -1,16 +1,51 @@
|
|
|
1
|
-
import { MemoryLayerError, AuthenticationError, AuthorizationError, NotFoundError, ValidationError } from "./errors.js";
|
|
1
|
+
import { MemoryLayerError, AuthenticationError, AuthorizationError, NotFoundError, ValidationError, EnterpriseRequiredError, RateLimitError } from "./errors.js";
|
|
2
|
+
import { sleep } from "./utils.js";
|
|
3
|
+
import { SkillsNamespace } from "./skills.js";
|
|
4
|
+
import { McpServersNamespace } from "./mcp_servers.js";
|
|
5
|
+
import { KnowledgebaseNamespace } from "./knowledgebase.js";
|
|
6
|
+
import { RpgNamespace } from "./rpg.js";
|
|
2
7
|
export class MemoryLayerClient {
|
|
3
8
|
baseUrl;
|
|
4
9
|
apiKey;
|
|
5
10
|
workspaceId;
|
|
6
11
|
sessionId;
|
|
7
12
|
timeout;
|
|
13
|
+
defaultAuthority;
|
|
14
|
+
fetchImpl;
|
|
15
|
+
maxRetries;
|
|
16
|
+
retryBaseDelay;
|
|
17
|
+
/** Skills namespace — access via `client.skills.list(...)` etc. */
|
|
18
|
+
skills;
|
|
19
|
+
/** MCP Servers namespace — access via `client.mcpServers.list(...)` etc. */
|
|
20
|
+
mcpServers;
|
|
21
|
+
/** Knowledgebase namespace — access via `client.kb.get(...)` etc. */
|
|
22
|
+
kb;
|
|
23
|
+
/** Repository Planning Graph namespace — access via `client.rpg.sync(...)` etc. */
|
|
24
|
+
rpg;
|
|
8
25
|
constructor(config = {}) {
|
|
9
26
|
this.baseUrl = config.baseUrl ?? "http://localhost:61001";
|
|
10
27
|
this.apiKey = config.apiKey;
|
|
11
28
|
this.workspaceId = config.workspaceId;
|
|
12
29
|
this.sessionId = config.sessionId;
|
|
13
30
|
this.timeout = config.timeout ?? 30000;
|
|
31
|
+
this.defaultAuthority = config.defaultAuthority;
|
|
32
|
+
this.maxRetries = config.maxRetries ?? 3;
|
|
33
|
+
this.retryBaseDelay = config.retryBaseDelay ?? 500;
|
|
34
|
+
// Bind to globalThis so the default impl preserves `this === undefined`
|
|
35
|
+
// (fetch is sensitive to its receiver in some runtimes).
|
|
36
|
+
this.fetchImpl = config.fetch ?? fetch.bind(globalThis);
|
|
37
|
+
this.skills = new SkillsNamespace(this);
|
|
38
|
+
this.mcpServers = new McpServersNamespace(this);
|
|
39
|
+
this.kb = new KnowledgebaseNamespace(this);
|
|
40
|
+
this.rpg = new RpgNamespace(this);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns a lightweight proxy that sends OBO headers for the given grant/subject
|
|
44
|
+
* on every request. The proxy is synchronous and reuses the parent client's
|
|
45
|
+
* connection settings — safe for concurrent use across multiple subjects.
|
|
46
|
+
*/
|
|
47
|
+
actingFor(opts) {
|
|
48
|
+
return new OboProxy(this, { grantId: opts.grantId, subject: opts.subject });
|
|
14
49
|
}
|
|
15
50
|
/**
|
|
16
51
|
* Set the active session ID. All subsequent requests will include
|
|
@@ -32,10 +67,36 @@ export class MemoryLayerClient {
|
|
|
32
67
|
getSessionId() {
|
|
33
68
|
return this.sessionId;
|
|
34
69
|
}
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
70
|
+
buildAuthorityHeaders(authority) {
|
|
71
|
+
const resolved = authority ?? this.defaultAuthority;
|
|
72
|
+
if (!resolved)
|
|
73
|
+
return {};
|
|
74
|
+
const h = {
|
|
75
|
+
"X-Aether-Grant-ID": resolved.grantId,
|
|
76
|
+
"X-Aether-Authority-Mode": "on_behalf_of",
|
|
77
|
+
"X-Aether-Subject-Type": resolved.subject.type,
|
|
78
|
+
"X-Aether-Subject-ID": resolved.subject.id,
|
|
38
79
|
};
|
|
80
|
+
return h;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the standard request headers (auth, session, workspace, OBO authority).
|
|
84
|
+
* Centralized so every request helper applies identical header logic.
|
|
85
|
+
*
|
|
86
|
+
* Note: upload/stream paths (exportWorkspace, importWorkspaceStream,
|
|
87
|
+
* uploadDocument, uploadDataset, getPageImage) call this method and therefore
|
|
88
|
+
* intentionally inherit authority (X-Aether-*) headers when a defaultAuthority
|
|
89
|
+
* is set — consistent with every other request type.
|
|
90
|
+
*
|
|
91
|
+
* @param includeContentType Whether to set `Content-Type: application/json`.
|
|
92
|
+
* Omit for multipart/form-data (browser sets the boundary) or NDJSON bodies
|
|
93
|
+
* that set their own content type.
|
|
94
|
+
*/
|
|
95
|
+
buildHeaders(authority, includeContentType = true) {
|
|
96
|
+
const headers = {};
|
|
97
|
+
if (includeContentType) {
|
|
98
|
+
headers["Content-Type"] = "application/json";
|
|
99
|
+
}
|
|
39
100
|
if (this.apiKey) {
|
|
40
101
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
41
102
|
}
|
|
@@ -45,32 +106,124 @@ export class MemoryLayerClient {
|
|
|
45
106
|
if (this.workspaceId) {
|
|
46
107
|
headers["X-Workspace-ID"] = this.workspaceId;
|
|
47
108
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
109
|
+
Object.assign(headers, this.buildAuthorityHeaders(authority));
|
|
110
|
+
return headers;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Determine whether a failed response is transient and should be retried.
|
|
114
|
+
* Retries 429 (rate limit) and 5xx server errors. 501 is treated as a
|
|
115
|
+
* permanent "not implemented" signal and is never retried.
|
|
116
|
+
*/
|
|
117
|
+
isRetryableStatus(status) {
|
|
118
|
+
if (status === 429)
|
|
119
|
+
return true;
|
|
120
|
+
if (status === 501)
|
|
121
|
+
return false;
|
|
122
|
+
return status >= 500 && status < 600;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Parse a `Retry-After` header (delta-seconds or HTTP-date) into milliseconds.
|
|
126
|
+
* Returns undefined when absent or unparseable.
|
|
127
|
+
*/
|
|
128
|
+
parseRetryAfter(response) {
|
|
129
|
+
const raw = response.headers?.get?.("Retry-After");
|
|
130
|
+
if (!raw)
|
|
131
|
+
return undefined;
|
|
132
|
+
const seconds = Number(raw);
|
|
133
|
+
if (!Number.isNaN(seconds)) {
|
|
134
|
+
return Math.max(0, seconds * 1000);
|
|
135
|
+
}
|
|
136
|
+
const dateMs = Date.parse(raw);
|
|
137
|
+
if (!Number.isNaN(dateMs)) {
|
|
138
|
+
return Math.max(0, dateMs - Date.now());
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Returns true when the HTTP method is safe to retry after a transient failure.
|
|
144
|
+
* POST is excluded to prevent duplicate writes (e.g. duplicate memories/edges
|
|
145
|
+
* after a post-commit 504). recall/reflect/mergeEntities are POST-but-read or
|
|
146
|
+
* write-once, but are deliberately left non-retried for safety and consistency
|
|
147
|
+
* with the Python SDK.
|
|
148
|
+
*/
|
|
149
|
+
isIdempotentMethod(method) {
|
|
150
|
+
return ["GET", "HEAD", "OPTIONS", "PUT", "DELETE", "PATCH"].includes(method.toUpperCase());
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Execute a fetch with bounded retry-with-backoff for transient failures
|
|
154
|
+
* (5xx, 429). Honors the `Retry-After` header when present, otherwise uses
|
|
155
|
+
* exponential backoff. Non-transient failures and successful responses are
|
|
156
|
+
* returned/raised immediately.
|
|
157
|
+
*
|
|
158
|
+
* Retries are gated on method idempotency: only GET/HEAD/OPTIONS/PUT/DELETE/PATCH
|
|
159
|
+
* are retried. POST is never retried automatically to avoid duplicate writes.
|
|
160
|
+
*
|
|
161
|
+
* This is the single network seam used by every request helper, so retry and
|
|
162
|
+
* header handling stay consistent across the client.
|
|
163
|
+
*/
|
|
164
|
+
async executeFetch(url, init, enterpriseFeature) {
|
|
165
|
+
const canRetry = this.isIdempotentMethod(init.method ?? "GET");
|
|
166
|
+
let lastError;
|
|
167
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
170
|
+
try {
|
|
171
|
+
const response = await this.fetchImpl(url, { ...init, signal: controller.signal });
|
|
172
|
+
clearTimeout(timeoutId);
|
|
173
|
+
if (response.ok) {
|
|
174
|
+
return response;
|
|
175
|
+
}
|
|
176
|
+
// Transient server-side failure: retry if attempts remain AND method is idempotent.
|
|
177
|
+
if (canRetry && attempt < this.maxRetries && this.isRetryableStatus(response.status)) {
|
|
178
|
+
const retryAfter = this.parseRetryAfter(response);
|
|
179
|
+
const delay = retryAfter ?? this.retryBaseDelay * Math.pow(2, attempt);
|
|
180
|
+
await sleep(delay);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
// Permanent failure, non-idempotent method, or retries exhausted: surface a typed error.
|
|
184
|
+
await this.handleError(response, enterpriseFeature);
|
|
61
185
|
}
|
|
62
|
-
|
|
63
|
-
|
|
186
|
+
catch (error) {
|
|
187
|
+
clearTimeout(timeoutId);
|
|
188
|
+
// Typed errors from handleError are terminal — never retry them.
|
|
189
|
+
if (error instanceof MemoryLayerError)
|
|
190
|
+
throw error;
|
|
191
|
+
// Network/abort error: retry if attempts remain AND method is idempotent.
|
|
192
|
+
lastError = error;
|
|
193
|
+
if (canRetry && attempt < this.maxRetries) {
|
|
194
|
+
await sleep(this.retryBaseDelay * Math.pow(2, attempt));
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
throw new MemoryLayerError(`Request failed: ${error}`);
|
|
64
198
|
}
|
|
65
|
-
return await response.json();
|
|
66
199
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
200
|
+
// Unreachable in practice, but satisfies the type checker.
|
|
201
|
+
throw new MemoryLayerError(`Request failed: ${lastError}`);
|
|
202
|
+
}
|
|
203
|
+
async request(method, path, body, enterpriseFeature, authority, responseMode = "json") {
|
|
204
|
+
const headers = this.buildHeaders(authority);
|
|
205
|
+
const url = `${this.baseUrl}${path}`;
|
|
206
|
+
const response = await this.executeFetch(url, {
|
|
207
|
+
method,
|
|
208
|
+
headers,
|
|
209
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
210
|
+
}, enterpriseFeature);
|
|
211
|
+
if (response.status === 204) {
|
|
212
|
+
return undefined;
|
|
71
213
|
}
|
|
214
|
+
// Non-JSON endpoints (e.g. GET /v1/skills/{id}/manifest → text/markdown,
|
|
215
|
+
// GET /v1/skills/{id}/files/{path} → text/x-python etc.) must be read as
|
|
216
|
+
// raw text/bytes; calling response.json() on them throws. Default stays
|
|
217
|
+
// JSON so existing callers are unaffected.
|
|
218
|
+
if (responseMode === "text") {
|
|
219
|
+
return await response.text();
|
|
220
|
+
}
|
|
221
|
+
if (responseMode === "arraybuffer") {
|
|
222
|
+
return await response.arrayBuffer();
|
|
223
|
+
}
|
|
224
|
+
return await response.json();
|
|
72
225
|
}
|
|
73
|
-
async handleError(response) {
|
|
226
|
+
async handleError(response, enterpriseFeature) {
|
|
74
227
|
const body = await response.json().catch(() => ({}));
|
|
75
228
|
const rawDetail = body.message ?? body.detail ?? response.statusText;
|
|
76
229
|
const message = typeof rawDetail === 'string'
|
|
@@ -86,6 +239,15 @@ export class MemoryLayerClient {
|
|
|
86
239
|
case 400:
|
|
87
240
|
case 422:
|
|
88
241
|
throw new ValidationError(message, body.details);
|
|
242
|
+
case 429: {
|
|
243
|
+
const retryAfterMs = this.parseRetryAfter(response);
|
|
244
|
+
throw new RateLimitError(message, retryAfterMs !== undefined ? retryAfterMs / 1000 : undefined);
|
|
245
|
+
}
|
|
246
|
+
case 501:
|
|
247
|
+
if (enterpriseFeature) {
|
|
248
|
+
throw new EnterpriseRequiredError(enterpriseFeature);
|
|
249
|
+
}
|
|
250
|
+
throw new NotFoundError(message);
|
|
89
251
|
default:
|
|
90
252
|
throw new MemoryLayerError(message, response.status);
|
|
91
253
|
}
|
|
@@ -102,9 +264,11 @@ export class MemoryLayerClient {
|
|
|
102
264
|
tags: options.tags ?? [],
|
|
103
265
|
metadata: options.metadata ?? {},
|
|
104
266
|
associations: options.associations ?? [],
|
|
267
|
+
relations: options.relations ?? [],
|
|
105
268
|
context_id: options.contextId,
|
|
269
|
+
user_id: options.userId,
|
|
106
270
|
};
|
|
107
|
-
const response = await this.request("POST", "/v1/memories", body);
|
|
271
|
+
const response = await this.request("POST", "/v1/memories", body, undefined, options.authority);
|
|
108
272
|
return response.memory;
|
|
109
273
|
}
|
|
110
274
|
async recall(query, options = {}) {
|
|
@@ -127,11 +291,21 @@ export class MemoryLayerClient {
|
|
|
127
291
|
max_expansion: options.maxExpansion,
|
|
128
292
|
created_after: options.createdAfter?.toISOString(),
|
|
129
293
|
created_before: options.createdBefore?.toISOString(),
|
|
294
|
+
offset: options.offset,
|
|
295
|
+
event_after: options.eventAfter?.toISOString(),
|
|
296
|
+
event_before: options.eventBefore?.toISOString(),
|
|
297
|
+
time_order: options.timeOrder,
|
|
298
|
+
include_global: options.includeGlobal,
|
|
299
|
+
include_global_user: options.includeGlobalUser,
|
|
130
300
|
context: options.conversationContext ?? [],
|
|
131
301
|
rag_threshold: options.ragThreshold,
|
|
132
302
|
detail_level: options.detailLevel,
|
|
303
|
+
user_id: options.userId,
|
|
304
|
+
budget_tokens: options.budgetTokens,
|
|
305
|
+
include_confidence: options.includeConfidence ?? true,
|
|
306
|
+
include_relations: options.includeRelations ?? true,
|
|
133
307
|
};
|
|
134
|
-
return this.request("POST", "/v1/memories/recall", body);
|
|
308
|
+
return this.request("POST", "/v1/memories/recall", body, undefined, options.authority);
|
|
135
309
|
}
|
|
136
310
|
async reflect(query, options = {}) {
|
|
137
311
|
const body = {
|
|
@@ -145,8 +319,9 @@ export class MemoryLayerClient {
|
|
|
145
319
|
subtypes: options.subtypes ?? [],
|
|
146
320
|
tags: options.tags ?? [],
|
|
147
321
|
context_id: options.contextId,
|
|
322
|
+
user_id: options.userId,
|
|
148
323
|
};
|
|
149
|
-
return this.request("POST", "/v1/memories/reflect", body);
|
|
324
|
+
return this.request("POST", "/v1/memories/reflect", body, undefined, options.authority);
|
|
150
325
|
}
|
|
151
326
|
async getMemory(memoryId) {
|
|
152
327
|
const response = await this.request("GET", `/v1/memories/${memoryId}`);
|
|
@@ -172,6 +347,54 @@ export class MemoryLayerClient {
|
|
|
172
347
|
const response = await this.request("GET", `/v1/memories/${memoryId}/associations?direction=${direction}`);
|
|
173
348
|
return response.associations;
|
|
174
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Update an association's strength and/or metadata.
|
|
352
|
+
*
|
|
353
|
+
* `memoryId` must be one of the association's endpoints (source or target);
|
|
354
|
+
* otherwise the server returns 404. Re-typing an edge is not supported —
|
|
355
|
+
* delete and recreate instead.
|
|
356
|
+
*/
|
|
357
|
+
async updateAssociation(memoryId, associationId, updates) {
|
|
358
|
+
const body = {};
|
|
359
|
+
if (updates.strength !== undefined)
|
|
360
|
+
body.strength = updates.strength;
|
|
361
|
+
if (updates.metadata !== undefined)
|
|
362
|
+
body.metadata = updates.metadata;
|
|
363
|
+
await this.request("PATCH", `/v1/memories/${memoryId}/associations/${associationId}`, body);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Delete an association (graph edge) by ID.
|
|
367
|
+
*
|
|
368
|
+
* `memoryId` must be one of the association's endpoints (source or target);
|
|
369
|
+
* otherwise the server returns 404.
|
|
370
|
+
*/
|
|
371
|
+
async deleteAssociation(memoryId, associationId) {
|
|
372
|
+
await this.request("DELETE", `/v1/memories/${memoryId}/associations/${associationId}`);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* List/browse memories in the workspace ordered by recency (created_at desc).
|
|
376
|
+
*
|
|
377
|
+
* Unlike {@link recall} this performs no vector search — it is a plain
|
|
378
|
+
* filtered enumeration for browsing. Supports limit/offset pagination and
|
|
379
|
+
* optional type/subtype/tag/context filters.
|
|
380
|
+
*/
|
|
381
|
+
async listMemories(options = {}) {
|
|
382
|
+
const params = new URLSearchParams();
|
|
383
|
+
if (options.limit !== undefined)
|
|
384
|
+
params.set("limit", String(options.limit));
|
|
385
|
+
if (options.offset !== undefined)
|
|
386
|
+
params.set("offset", String(options.offset));
|
|
387
|
+
if (options.type)
|
|
388
|
+
params.set("type", String(options.type));
|
|
389
|
+
if (options.subtype)
|
|
390
|
+
params.set("subtype", String(options.subtype));
|
|
391
|
+
if (options.tag)
|
|
392
|
+
params.set("tag", options.tag);
|
|
393
|
+
if (options.contextId)
|
|
394
|
+
params.set("context_id", options.contextId);
|
|
395
|
+
const query = params.toString();
|
|
396
|
+
return this.request("GET", `/v1/memories${query ? `?${query}` : ""}`);
|
|
397
|
+
}
|
|
175
398
|
// Session operations
|
|
176
399
|
/**
|
|
177
400
|
* Create a new session.
|
|
@@ -218,6 +441,37 @@ export class MemoryLayerClient {
|
|
|
218
441
|
const response = await this.request("GET", `/v1/sessions/${sessionId}`);
|
|
219
442
|
return response.session;
|
|
220
443
|
}
|
|
444
|
+
async createCheckpoint(sessionId, input) {
|
|
445
|
+
return this.request("POST", `/v1/sessions/${sessionId}/checkpoints`, {
|
|
446
|
+
transcript_segment: input.transcriptSegment,
|
|
447
|
+
content_hash: input.contentHash,
|
|
448
|
+
idempotency_key: input.idempotencyKey,
|
|
449
|
+
source_kind: input.sourceKind ?? "transcript",
|
|
450
|
+
source_sequence: input.sourceSequence,
|
|
451
|
+
source_boundary: input.sourceBoundary,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
async getCheckpoint(sessionId, checkpointId) {
|
|
455
|
+
return this.request("GET", `/v1/sessions/${sessionId}/checkpoints/${checkpointId}`);
|
|
456
|
+
}
|
|
457
|
+
async getContextPack(sessionId, options = {}) {
|
|
458
|
+
return this.request("POST", `/v1/sessions/${sessionId}/context-pack`, {
|
|
459
|
+
topic: options.topic,
|
|
460
|
+
entity_ids: options.entityIds ?? [],
|
|
461
|
+
entity_names: options.entityNames ?? [],
|
|
462
|
+
budget_tokens: options.budgetTokens ?? 2048,
|
|
463
|
+
section_limits: options.sectionLimits,
|
|
464
|
+
include_directives: options.includeDirectives,
|
|
465
|
+
include_working_memory: options.includeWorkingMemory,
|
|
466
|
+
include_recent_activity: options.includeRecentActivity,
|
|
467
|
+
include_contradictions: options.includeContradictions,
|
|
468
|
+
include_sandbox_summary: options.includeSandboxSummary,
|
|
469
|
+
include_checkpoint_recovery: options.includeCheckpointRecovery,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
async getContextDelta(sessionId, cursor, budgetTokens = 2048) {
|
|
473
|
+
return this.request("POST", `/v1/sessions/${sessionId}/context-delta`, { cursor, budget_tokens: budgetTokens });
|
|
474
|
+
}
|
|
221
475
|
async deleteSession(sessionId) {
|
|
222
476
|
await this.request("DELETE", `/v1/sessions/${sessionId}`);
|
|
223
477
|
}
|
|
@@ -275,8 +529,8 @@ export class MemoryLayerClient {
|
|
|
275
529
|
return response.briefing;
|
|
276
530
|
}
|
|
277
531
|
// Workspace operations
|
|
278
|
-
async createWorkspace(name, settings) {
|
|
279
|
-
const response = await this.request("POST", "/v1/workspaces", { name, settings: settings ?? {} });
|
|
532
|
+
async createWorkspace(name, settings, tags) {
|
|
533
|
+
const response = await this.request("POST", "/v1/workspaces", { name, settings: settings ?? {}, tags: tags ?? [] });
|
|
280
534
|
return response.workspace;
|
|
281
535
|
}
|
|
282
536
|
async getWorkspace(workspaceId) {
|
|
@@ -286,8 +540,16 @@ export class MemoryLayerClient {
|
|
|
286
540
|
const response = await this.request("GET", `/v1/workspaces/${id}`);
|
|
287
541
|
return response.workspace;
|
|
288
542
|
}
|
|
289
|
-
async listWorkspaces() {
|
|
290
|
-
|
|
543
|
+
async listWorkspaces(filter) {
|
|
544
|
+
let path = "/v1/workspaces";
|
|
545
|
+
if (filter?.tags && filter.tags.length > 0) {
|
|
546
|
+
const params = new URLSearchParams();
|
|
547
|
+
for (const tag of filter.tags)
|
|
548
|
+
params.append("tags", tag);
|
|
549
|
+
params.append("match", filter.match ?? "all");
|
|
550
|
+
path += `?${params.toString()}`;
|
|
551
|
+
}
|
|
552
|
+
const response = await this.request("GET", path);
|
|
291
553
|
return response.workspaces;
|
|
292
554
|
}
|
|
293
555
|
async updateWorkspace(workspaceId, updates) {
|
|
@@ -306,6 +568,116 @@ export class MemoryLayerClient {
|
|
|
306
568
|
const response = await this.request("GET", `/v1/workspaces/${this.workspaceId}/contexts`);
|
|
307
569
|
return response.contexts;
|
|
308
570
|
}
|
|
571
|
+
async deleteContext(contextId, workspaceId) {
|
|
572
|
+
const wsId = workspaceId ?? this.workspaceId;
|
|
573
|
+
if (!wsId)
|
|
574
|
+
throw new ValidationError("Workspace ID required");
|
|
575
|
+
await this.request("DELETE", `/v1/workspaces/${wsId}/contexts/${contextId}`);
|
|
576
|
+
}
|
|
577
|
+
// ------------------------------------------------------------------ //
|
|
578
|
+
// Entity Registry operations
|
|
579
|
+
//
|
|
580
|
+
// Gated server-side by MEMORYLAYER_ENTITY_REGISTRY_ENABLED. When the
|
|
581
|
+
// registry is disabled the server returns 501, surfaced here as
|
|
582
|
+
// EnterpriseRequiredError via the `enterpriseFeature` arg.
|
|
583
|
+
// ------------------------------------------------------------------ //
|
|
584
|
+
/** List canonical entities in a workspace (deterministic order by id). */
|
|
585
|
+
async listEntities(options = {}) {
|
|
586
|
+
const params = new URLSearchParams();
|
|
587
|
+
const wsId = options.workspaceId ?? this.workspaceId;
|
|
588
|
+
if (wsId)
|
|
589
|
+
params.set("workspace_id", wsId);
|
|
590
|
+
if (options.status)
|
|
591
|
+
params.set("status", options.status);
|
|
592
|
+
if (options.limit !== undefined)
|
|
593
|
+
params.set("limit", String(options.limit));
|
|
594
|
+
const query = params.toString();
|
|
595
|
+
return this.request("GET", `/v1/entities${query ? `?${query}` : ""}`, undefined, "Entity registry");
|
|
596
|
+
}
|
|
597
|
+
/** Get a single canonical entity by id. */
|
|
598
|
+
async getEntity(entityId, workspaceId) {
|
|
599
|
+
const params = new URLSearchParams();
|
|
600
|
+
const wsId = workspaceId ?? this.workspaceId;
|
|
601
|
+
if (wsId)
|
|
602
|
+
params.set("workspace_id", wsId);
|
|
603
|
+
const query = params.toString();
|
|
604
|
+
const response = await this.request("GET", `/v1/entities/${entityId}${query ? `?${query}` : ""}`, undefined, "Entity registry");
|
|
605
|
+
return response.entity;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Resolve a surface name/alias to an existing canonical entity (never
|
|
609
|
+
* creates). Throws NotFoundError if no entity matches.
|
|
610
|
+
*/
|
|
611
|
+
async resolveEntity(name, options = {}) {
|
|
612
|
+
const params = new URLSearchParams();
|
|
613
|
+
params.set("name", name);
|
|
614
|
+
if (options.entityType)
|
|
615
|
+
params.set("entity_type", String(options.entityType));
|
|
616
|
+
const wsId = options.workspaceId ?? this.workspaceId;
|
|
617
|
+
if (wsId)
|
|
618
|
+
params.set("workspace_id", wsId);
|
|
619
|
+
return this.request("GET", `/v1/entities/resolve?${params.toString()}`, undefined, "Entity registry");
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Merge `sourceId` into `targetId`; returns the surviving target entity.
|
|
623
|
+
* Requires the entity registry to be enabled (501 -> EnterpriseRequiredError).
|
|
624
|
+
*/
|
|
625
|
+
async mergeEntities(options) {
|
|
626
|
+
const params = new URLSearchParams();
|
|
627
|
+
const wsId = options.workspaceId ?? this.workspaceId;
|
|
628
|
+
if (wsId)
|
|
629
|
+
params.set("workspace_id", wsId);
|
|
630
|
+
const query = params.toString();
|
|
631
|
+
const response = await this.request("POST", `/v1/entities/merge${query ? `?${query}` : ""}`, {
|
|
632
|
+
source_id: options.sourceId,
|
|
633
|
+
target_id: options.targetId,
|
|
634
|
+
reason: options.reason,
|
|
635
|
+
}, "Entity registry");
|
|
636
|
+
return response.entity;
|
|
637
|
+
}
|
|
638
|
+
// ------------------------------------------------------------------ //
|
|
639
|
+
// API Token operations (admin scope; gRPC-backed via Aether)
|
|
640
|
+
// ------------------------------------------------------------------ //
|
|
641
|
+
/** List API tokens. Set `includeRevoked` to also return revoked tokens. */
|
|
642
|
+
async listTokens(includeRevoked = false) {
|
|
643
|
+
const params = new URLSearchParams();
|
|
644
|
+
if (includeRevoked)
|
|
645
|
+
params.set("include_revoked", "true");
|
|
646
|
+
const query = params.toString();
|
|
647
|
+
const response = await this.request("GET", `/v1/tokens${query ? `?${query}` : ""}`);
|
|
648
|
+
return response.tokens;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Create a new API token. The returned object includes the plaintext
|
|
652
|
+
* `token` value, which is only ever available at creation time.
|
|
653
|
+
*/
|
|
654
|
+
async createToken(options) {
|
|
655
|
+
const body = { name: options.name };
|
|
656
|
+
if (options.principalType !== undefined)
|
|
657
|
+
body.principal_type = options.principalType;
|
|
658
|
+
if (options.workspacePatterns !== undefined)
|
|
659
|
+
body.workspace_patterns = options.workspacePatterns;
|
|
660
|
+
if (options.scopes !== undefined)
|
|
661
|
+
body.scopes = options.scopes;
|
|
662
|
+
if (options.expiresInDays !== undefined)
|
|
663
|
+
body.expires_in_days = options.expiresInDays;
|
|
664
|
+
return this.request("POST", "/v1/tokens", body);
|
|
665
|
+
}
|
|
666
|
+
/** Get details for a single API token. */
|
|
667
|
+
async getToken(tokenId) {
|
|
668
|
+
return this.request("GET", `/v1/tokens/${tokenId}`);
|
|
669
|
+
}
|
|
670
|
+
/** Delete an API token. */
|
|
671
|
+
async deleteToken(tokenId) {
|
|
672
|
+
await this.request("DELETE", `/v1/tokens/${tokenId}`);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Revoke an API token. Revoked tokens are invalidated immediately but remain
|
|
676
|
+
* visible in listings (with `revoked=true`).
|
|
677
|
+
*/
|
|
678
|
+
async revokeToken(tokenId) {
|
|
679
|
+
await this.request("POST", `/v1/tokens/${tokenId}/revoke`);
|
|
680
|
+
}
|
|
309
681
|
// Batch operations
|
|
310
682
|
async batchMemories(operations) {
|
|
311
683
|
return this.request("POST", "/v1/memories/batch", { operations });
|
|
@@ -362,20 +734,11 @@ export class MemoryLayerClient {
|
|
|
362
734
|
const query = params.toString() ? `?${params.toString()}` : '';
|
|
363
735
|
// Fetch NDJSON response
|
|
364
736
|
const url = `${this.baseUrl}/v1/workspaces/${id}/export${query}`;
|
|
365
|
-
const headers =
|
|
366
|
-
if (this.apiKey) {
|
|
367
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
368
|
-
}
|
|
369
|
-
if (this.sessionId) {
|
|
370
|
-
headers["X-Session-ID"] = this.sessionId;
|
|
371
|
-
}
|
|
372
|
-
if (this.workspaceId) {
|
|
373
|
-
headers["X-Workspace-ID"] = this.workspaceId;
|
|
374
|
-
}
|
|
737
|
+
const headers = this.buildHeaders(undefined, false);
|
|
375
738
|
const controller = new AbortController();
|
|
376
739
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
377
740
|
try {
|
|
378
|
-
const response = await
|
|
741
|
+
const response = await this.fetchImpl(url, {
|
|
379
742
|
method: "GET",
|
|
380
743
|
headers,
|
|
381
744
|
signal: controller.signal,
|
|
@@ -435,20 +798,11 @@ export class MemoryLayerClient {
|
|
|
435
798
|
}
|
|
436
799
|
const query = params.toString() ? `?${params.toString()}` : '';
|
|
437
800
|
const url = `${this.baseUrl}/v1/workspaces/${id}/export${query}`;
|
|
438
|
-
const headers =
|
|
439
|
-
if (this.apiKey) {
|
|
440
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
441
|
-
}
|
|
442
|
-
if (this.sessionId) {
|
|
443
|
-
headers["X-Session-ID"] = this.sessionId;
|
|
444
|
-
}
|
|
445
|
-
if (this.workspaceId) {
|
|
446
|
-
headers["X-Workspace-ID"] = this.workspaceId;
|
|
447
|
-
}
|
|
801
|
+
const headers = this.buildHeaders(undefined, false);
|
|
448
802
|
const controller = new AbortController();
|
|
449
803
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
450
804
|
try {
|
|
451
|
-
const response = await
|
|
805
|
+
const response = await this.fetchImpl(url, {
|
|
452
806
|
method: "GET",
|
|
453
807
|
headers,
|
|
454
808
|
signal: controller.signal,
|
|
@@ -475,22 +829,12 @@ export class MemoryLayerClient {
|
|
|
475
829
|
}
|
|
476
830
|
async importWorkspaceStream(workspaceId, ndjsonBody) {
|
|
477
831
|
const url = `${this.baseUrl}/v1/workspaces/${workspaceId}/import`;
|
|
478
|
-
const headers =
|
|
479
|
-
|
|
480
|
-
};
|
|
481
|
-
if (this.apiKey) {
|
|
482
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
483
|
-
}
|
|
484
|
-
if (this.sessionId) {
|
|
485
|
-
headers["X-Session-ID"] = this.sessionId;
|
|
486
|
-
}
|
|
487
|
-
if (this.workspaceId) {
|
|
488
|
-
headers["X-Workspace-ID"] = this.workspaceId;
|
|
489
|
-
}
|
|
832
|
+
const headers = this.buildHeaders(undefined, false);
|
|
833
|
+
headers["Content-Type"] = "application/x-ndjson";
|
|
490
834
|
const controller = new AbortController();
|
|
491
835
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
492
836
|
try {
|
|
493
|
-
const response = await
|
|
837
|
+
const response = await this.fetchImpl(url, {
|
|
494
838
|
method: "POST",
|
|
495
839
|
headers,
|
|
496
840
|
body: ndjsonBody,
|
|
@@ -577,5 +921,490 @@ export class MemoryLayerClient {
|
|
|
577
921
|
async contextCheckpoint() {
|
|
578
922
|
await this.request("POST", "/v1/context/checkpoint");
|
|
579
923
|
}
|
|
924
|
+
// ------------------------------------------------------------------ //
|
|
925
|
+
// Document operations (Enterprise)
|
|
926
|
+
// ------------------------------------------------------------------ //
|
|
927
|
+
/**
|
|
928
|
+
* Upload a document for ingestion.
|
|
929
|
+
*
|
|
930
|
+
* Requires MemoryLayer Enterprise. On OSS servers this throws
|
|
931
|
+
* `EnterpriseRequiredError`.
|
|
932
|
+
*/
|
|
933
|
+
async uploadDocument(file, filename, options = {}) {
|
|
934
|
+
const formData = new FormData();
|
|
935
|
+
formData.append("file", file, filename);
|
|
936
|
+
if (options.targetContextId)
|
|
937
|
+
formData.append("target_context_id", options.targetContextId);
|
|
938
|
+
if (options.chunkingStrategy)
|
|
939
|
+
formData.append("chunking_strategy", options.chunkingStrategy);
|
|
940
|
+
if (options.chunkSize !== undefined)
|
|
941
|
+
formData.append("chunk_size", String(options.chunkSize));
|
|
942
|
+
if (options.chunkOverlap !== undefined)
|
|
943
|
+
formData.append("chunk_overlap", String(options.chunkOverlap));
|
|
944
|
+
if (options.importance !== undefined)
|
|
945
|
+
formData.append("importance", String(options.importance));
|
|
946
|
+
if (options.retainOriginal !== undefined)
|
|
947
|
+
formData.append("retain_original", String(options.retainOriginal));
|
|
948
|
+
const headers = this.buildHeaders(undefined, false);
|
|
949
|
+
const url = `${this.baseUrl}/v1/documents`;
|
|
950
|
+
const controller = new AbortController();
|
|
951
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
952
|
+
try {
|
|
953
|
+
const response = await this.fetchImpl(url, {
|
|
954
|
+
method: "POST",
|
|
955
|
+
headers,
|
|
956
|
+
body: formData,
|
|
957
|
+
signal: controller.signal,
|
|
958
|
+
});
|
|
959
|
+
clearTimeout(timeoutId);
|
|
960
|
+
if (!response.ok) {
|
|
961
|
+
await this.handleError(response, "Document ingestion");
|
|
962
|
+
}
|
|
963
|
+
return await response.json();
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
if (error instanceof MemoryLayerError)
|
|
967
|
+
throw error;
|
|
968
|
+
throw new MemoryLayerError(`Document upload failed: ${error}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* List documents in the workspace.
|
|
973
|
+
*/
|
|
974
|
+
async listDocuments(options) {
|
|
975
|
+
const params = new URLSearchParams();
|
|
976
|
+
if (options?.status)
|
|
977
|
+
params.set("status", options.status);
|
|
978
|
+
if (options?.limit !== undefined)
|
|
979
|
+
params.set("limit", String(options.limit));
|
|
980
|
+
if (options?.offset !== undefined)
|
|
981
|
+
params.set("offset", String(options.offset));
|
|
982
|
+
const query = params.toString();
|
|
983
|
+
return this.request("GET", `/v1/documents${query ? `?${query}` : ""}`, undefined, "Document management");
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Get document metadata and processing status.
|
|
987
|
+
*/
|
|
988
|
+
async getDocument(documentId) {
|
|
989
|
+
const response = await this.request("GET", `/v1/documents/${documentId}`, undefined, "Document management");
|
|
990
|
+
return response;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Delete a document and optionally its extracted memories.
|
|
994
|
+
*/
|
|
995
|
+
async deleteDocument(documentId, deleteMemories = false) {
|
|
996
|
+
await this.request("DELETE", `/v1/documents/${documentId}?delete_memories=${deleteMemories}`, undefined, "Document management");
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Search document pages using ColPali MaxSim visual similarity.
|
|
1000
|
+
*
|
|
1001
|
+
* Requires MemoryLayer Enterprise.
|
|
1002
|
+
*/
|
|
1003
|
+
async searchDocumentPages(query, options = {}) {
|
|
1004
|
+
const body = {
|
|
1005
|
+
query,
|
|
1006
|
+
limit: options.limit ?? 10,
|
|
1007
|
+
doc_ids: options.docIds,
|
|
1008
|
+
};
|
|
1009
|
+
return this.request("POST", "/v1/documents/search", body, "Document page search");
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Get all pages for a document.
|
|
1013
|
+
*/
|
|
1014
|
+
async getDocumentPages(documentId) {
|
|
1015
|
+
return this.request("GET", `/v1/documents/${documentId}/pages`, undefined, "Document pages");
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Get a page image as a Blob.
|
|
1019
|
+
*/
|
|
1020
|
+
async getPageImage(documentId, pageId) {
|
|
1021
|
+
const headers = this.buildHeaders(undefined, false);
|
|
1022
|
+
const url = `${this.baseUrl}/v1/documents/${documentId}/pages/${pageId}/image`;
|
|
1023
|
+
const controller = new AbortController();
|
|
1024
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1025
|
+
try {
|
|
1026
|
+
const response = await this.fetchImpl(url, { method: "GET", headers, signal: controller.signal });
|
|
1027
|
+
clearTimeout(timeoutId);
|
|
1028
|
+
if (!response.ok) {
|
|
1029
|
+
await this.handleError(response, "Document page images");
|
|
1030
|
+
}
|
|
1031
|
+
return await response.blob();
|
|
1032
|
+
}
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
if (error instanceof MemoryLayerError)
|
|
1035
|
+
throw error;
|
|
1036
|
+
throw new MemoryLayerError(`Page image request failed: ${error}`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Get ingestion job status.
|
|
1041
|
+
*/
|
|
1042
|
+
async getJob(jobId) {
|
|
1043
|
+
return this.request("GET", `/v1/documents/jobs/${jobId}`, undefined, "Document ingestion jobs");
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* List ingestion jobs in the workspace.
|
|
1047
|
+
*/
|
|
1048
|
+
async listJobs(options) {
|
|
1049
|
+
const params = new URLSearchParams();
|
|
1050
|
+
if (options?.status)
|
|
1051
|
+
params.set("status", options.status);
|
|
1052
|
+
if (options?.limit !== undefined)
|
|
1053
|
+
params.set("limit", String(options.limit));
|
|
1054
|
+
const query = params.toString();
|
|
1055
|
+
return this.request("GET", `/v1/documents/jobs${query ? `?${query}` : ""}`, undefined, "Document ingestion jobs");
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Cancel a running ingestion job.
|
|
1059
|
+
*/
|
|
1060
|
+
async cancelJob(jobId) {
|
|
1061
|
+
await this.request("POST", `/v1/documents/jobs/${jobId}/cancel`, undefined, "Document ingestion jobs");
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Reprocess a document with optionally different extraction options.
|
|
1065
|
+
*/
|
|
1066
|
+
async reprocessDocument(documentId, options) {
|
|
1067
|
+
const body = {};
|
|
1068
|
+
if (options?.targetContextId)
|
|
1069
|
+
body.target_context_id = options.targetContextId;
|
|
1070
|
+
if (options?.chunkingStrategy)
|
|
1071
|
+
body.chunking_strategy = options.chunkingStrategy;
|
|
1072
|
+
if (options?.chunkSize !== undefined)
|
|
1073
|
+
body.chunk_size = options.chunkSize;
|
|
1074
|
+
if (options?.chunkOverlap !== undefined)
|
|
1075
|
+
body.chunk_overlap = options.chunkOverlap;
|
|
1076
|
+
if (options?.importance !== undefined)
|
|
1077
|
+
body.importance = options.importance;
|
|
1078
|
+
return this.request("POST", `/v1/documents/${documentId}/reprocess`, Object.keys(body).length ? body : undefined, "Document reprocessing");
|
|
1079
|
+
}
|
|
1080
|
+
// ------------------------------------------------------------------ //
|
|
1081
|
+
// Chat History operations
|
|
1082
|
+
// ------------------------------------------------------------------ //
|
|
1083
|
+
async createThread(options = {}) {
|
|
1084
|
+
const body = {
|
|
1085
|
+
thread_id: options.threadId,
|
|
1086
|
+
workspace_id: options.workspaceId ?? this.workspaceId,
|
|
1087
|
+
user_id: options.userId,
|
|
1088
|
+
context_id: options.contextId,
|
|
1089
|
+
observer_id: options.observerId,
|
|
1090
|
+
subject_id: options.subjectId,
|
|
1091
|
+
title: options.title,
|
|
1092
|
+
metadata: options.metadata,
|
|
1093
|
+
expires_at: options.expiresAt,
|
|
1094
|
+
};
|
|
1095
|
+
const response = await this.request("POST", "/v1/threads", body);
|
|
1096
|
+
return response.thread;
|
|
1097
|
+
}
|
|
1098
|
+
async listThreads(options = {}) {
|
|
1099
|
+
const params = new URLSearchParams();
|
|
1100
|
+
const wsId = options.workspaceId ?? this.workspaceId;
|
|
1101
|
+
if (wsId)
|
|
1102
|
+
params.set("workspace_id", wsId);
|
|
1103
|
+
if (options.userId)
|
|
1104
|
+
params.set("user_id", options.userId);
|
|
1105
|
+
if (options.limit !== undefined)
|
|
1106
|
+
params.set("limit", String(options.limit));
|
|
1107
|
+
if (options.offset !== undefined)
|
|
1108
|
+
params.set("offset", String(options.offset));
|
|
1109
|
+
const query = params.toString();
|
|
1110
|
+
const response = await this.request("GET", `/v1/threads${query ? `?${query}` : ""}`);
|
|
1111
|
+
return response.threads;
|
|
1112
|
+
}
|
|
1113
|
+
async getThread(threadId, workspaceId) {
|
|
1114
|
+
const params = new URLSearchParams();
|
|
1115
|
+
const wsId = workspaceId ?? this.workspaceId;
|
|
1116
|
+
if (wsId)
|
|
1117
|
+
params.set("workspace_id", wsId);
|
|
1118
|
+
const query = params.toString();
|
|
1119
|
+
const response = await this.request("GET", `/v1/threads/${threadId}${query ? `?${query}` : ""}`);
|
|
1120
|
+
return response.thread;
|
|
1121
|
+
}
|
|
1122
|
+
async getThreadFull(threadId, options) {
|
|
1123
|
+
const params = new URLSearchParams();
|
|
1124
|
+
const wsId = options?.workspaceId ?? this.workspaceId;
|
|
1125
|
+
if (wsId)
|
|
1126
|
+
params.set("workspace_id", wsId);
|
|
1127
|
+
if (options?.limit !== undefined)
|
|
1128
|
+
params.set("limit", String(options.limit));
|
|
1129
|
+
if (options?.offset !== undefined)
|
|
1130
|
+
params.set("offset", String(options.offset));
|
|
1131
|
+
if (options?.order)
|
|
1132
|
+
params.set("order", options.order);
|
|
1133
|
+
const query = params.toString();
|
|
1134
|
+
return this.request("GET", `/v1/threads/${threadId}/full${query ? `?${query}` : ""}`);
|
|
1135
|
+
}
|
|
1136
|
+
async deleteThread(threadId, workspaceId) {
|
|
1137
|
+
const params = new URLSearchParams();
|
|
1138
|
+
const wsId = workspaceId ?? this.workspaceId;
|
|
1139
|
+
if (wsId)
|
|
1140
|
+
params.set("workspace_id", wsId);
|
|
1141
|
+
const query = params.toString();
|
|
1142
|
+
await this.request("DELETE", `/v1/threads/${threadId}${query ? `?${query}` : ""}`);
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* List chat threads owned by a user across all workspaces.
|
|
1146
|
+
*
|
|
1147
|
+
* Unlike {@link listThreads}, this is keyed on (tenant, user, ownership) and
|
|
1148
|
+
* does NOT require a workspace filter — used for user-session-scoped views.
|
|
1149
|
+
*/
|
|
1150
|
+
async listUserThreads(userId, options = {}) {
|
|
1151
|
+
const params = new URLSearchParams();
|
|
1152
|
+
if (options.ownership)
|
|
1153
|
+
params.set("ownership", options.ownership);
|
|
1154
|
+
if (options.scopeFilter)
|
|
1155
|
+
params.set("scope_filter", options.scopeFilter);
|
|
1156
|
+
if (options.limit !== undefined)
|
|
1157
|
+
params.set("limit", String(options.limit));
|
|
1158
|
+
if (options.offset !== undefined)
|
|
1159
|
+
params.set("offset", String(options.offset));
|
|
1160
|
+
const query = params.toString();
|
|
1161
|
+
const response = await this.request("GET", `/v1/threads/user/${userId}${query ? `?${query}` : ""}`);
|
|
1162
|
+
return response.threads;
|
|
1163
|
+
}
|
|
1164
|
+
/** Update a thread (e.g. rename or change metadata). */
|
|
1165
|
+
async updateThread(threadId, updates) {
|
|
1166
|
+
const params = new URLSearchParams();
|
|
1167
|
+
const wsId = updates.workspaceId ?? this.workspaceId;
|
|
1168
|
+
if (wsId)
|
|
1169
|
+
params.set("workspace_id", wsId);
|
|
1170
|
+
const query = params.toString();
|
|
1171
|
+
const body = {};
|
|
1172
|
+
if (updates.title !== undefined)
|
|
1173
|
+
body.title = updates.title;
|
|
1174
|
+
if (updates.metadata !== undefined)
|
|
1175
|
+
body.metadata = updates.metadata;
|
|
1176
|
+
const response = await this.request("PUT", `/v1/threads/${threadId}${query ? `?${query}` : ""}`, body);
|
|
1177
|
+
return response.thread;
|
|
1178
|
+
}
|
|
1179
|
+
/** Delete a single message from a thread. */
|
|
1180
|
+
async deleteMessage(threadId, messageId, workspaceId) {
|
|
1181
|
+
const params = new URLSearchParams();
|
|
1182
|
+
const wsId = workspaceId ?? this.workspaceId;
|
|
1183
|
+
if (wsId)
|
|
1184
|
+
params.set("workspace_id", wsId);
|
|
1185
|
+
const query = params.toString();
|
|
1186
|
+
await this.request("DELETE", `/v1/threads/${threadId}/messages/${messageId}${query ? `?${query}` : ""}`);
|
|
1187
|
+
}
|
|
1188
|
+
async appendMessages(threadId, messages, workspaceId) {
|
|
1189
|
+
const params = new URLSearchParams();
|
|
1190
|
+
const wsId = workspaceId ?? this.workspaceId;
|
|
1191
|
+
if (wsId)
|
|
1192
|
+
params.set("workspace_id", wsId);
|
|
1193
|
+
const query = params.toString();
|
|
1194
|
+
return this.request("POST", `/v1/threads/${threadId}/messages${query ? `?${query}` : ""}`, { messages });
|
|
1195
|
+
}
|
|
1196
|
+
async getMessages(threadId, options) {
|
|
1197
|
+
const params = new URLSearchParams();
|
|
1198
|
+
const wsId = options?.workspaceId ?? this.workspaceId;
|
|
1199
|
+
if (wsId)
|
|
1200
|
+
params.set("workspace_id", wsId);
|
|
1201
|
+
if (options?.limit !== undefined)
|
|
1202
|
+
params.set("limit", String(options.limit));
|
|
1203
|
+
if (options?.offset !== undefined)
|
|
1204
|
+
params.set("offset", String(options.offset));
|
|
1205
|
+
if (options?.afterIndex !== undefined)
|
|
1206
|
+
params.set("after_index", String(options.afterIndex));
|
|
1207
|
+
if (options?.order)
|
|
1208
|
+
params.set("order", options.order);
|
|
1209
|
+
const query = params.toString();
|
|
1210
|
+
return this.request("GET", `/v1/threads/${threadId}/messages${query ? `?${query}` : ""}`);
|
|
1211
|
+
}
|
|
1212
|
+
async decomposeThread(threadId, workspaceId) {
|
|
1213
|
+
const params = new URLSearchParams();
|
|
1214
|
+
const wsId = workspaceId ?? this.workspaceId;
|
|
1215
|
+
if (wsId)
|
|
1216
|
+
params.set("workspace_id", wsId);
|
|
1217
|
+
const query = params.toString();
|
|
1218
|
+
return this.request("POST", `/v1/threads/${threadId}/decompose${query ? `?${query}` : ""}`);
|
|
1219
|
+
}
|
|
1220
|
+
// ------------------------------------------------------------------ //
|
|
1221
|
+
// Dataset operations (Enterprise)
|
|
1222
|
+
// ------------------------------------------------------------------ //
|
|
1223
|
+
/**
|
|
1224
|
+
* Upload a dataset for profiling and memory extraction.
|
|
1225
|
+
*
|
|
1226
|
+
* Requires MemoryLayer Enterprise. On OSS servers this throws
|
|
1227
|
+
* `EnterpriseRequiredError`.
|
|
1228
|
+
*/
|
|
1229
|
+
async uploadDataset(file, filename, options = {}) {
|
|
1230
|
+
const formData = new FormData();
|
|
1231
|
+
formData.append("file", file, filename);
|
|
1232
|
+
if (options.name)
|
|
1233
|
+
formData.append("name", options.name);
|
|
1234
|
+
if (options.targetContextId)
|
|
1235
|
+
formData.append("target_context_id", options.targetContextId);
|
|
1236
|
+
if (options.importance !== undefined)
|
|
1237
|
+
formData.append("importance", String(options.importance));
|
|
1238
|
+
if (options.sampleRows !== undefined)
|
|
1239
|
+
formData.append("sample_rows", String(options.sampleRows));
|
|
1240
|
+
if (options.detectTimeSeries !== undefined)
|
|
1241
|
+
formData.append("detect_time_series", String(options.detectTimeSeries));
|
|
1242
|
+
if (options.generateSummaries !== undefined)
|
|
1243
|
+
formData.append("generate_summaries", String(options.generateSummaries));
|
|
1244
|
+
const headers = this.buildHeaders(undefined, false);
|
|
1245
|
+
const url = `${this.baseUrl}/v1/datasets`;
|
|
1246
|
+
const controller = new AbortController();
|
|
1247
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1248
|
+
try {
|
|
1249
|
+
const response = await this.fetchImpl(url, {
|
|
1250
|
+
method: "POST",
|
|
1251
|
+
headers,
|
|
1252
|
+
body: formData,
|
|
1253
|
+
signal: controller.signal,
|
|
1254
|
+
});
|
|
1255
|
+
clearTimeout(timeoutId);
|
|
1256
|
+
if (!response.ok) {
|
|
1257
|
+
await this.handleError(response, "Dataset management");
|
|
1258
|
+
}
|
|
1259
|
+
return await response.json();
|
|
1260
|
+
}
|
|
1261
|
+
catch (error) {
|
|
1262
|
+
if (error instanceof MemoryLayerError)
|
|
1263
|
+
throw error;
|
|
1264
|
+
throw new MemoryLayerError(`Dataset upload failed: ${error}`);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* List datasets in the workspace.
|
|
1269
|
+
*/
|
|
1270
|
+
async listDatasets(options) {
|
|
1271
|
+
const params = new URLSearchParams();
|
|
1272
|
+
if (options?.status)
|
|
1273
|
+
params.set("status", options.status);
|
|
1274
|
+
if (options?.limit !== undefined)
|
|
1275
|
+
params.set("limit", String(options.limit));
|
|
1276
|
+
if (options?.offset !== undefined)
|
|
1277
|
+
params.set("offset", String(options.offset));
|
|
1278
|
+
const query = params.toString();
|
|
1279
|
+
return this.request("GET", `/v1/datasets${query ? `?${query}` : ""}`, undefined, "Dataset management");
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Get dataset metadata, schema, and profile.
|
|
1283
|
+
*/
|
|
1284
|
+
async getDataset(datasetId) {
|
|
1285
|
+
return this.request("GET", `/v1/datasets/${datasetId}`, undefined, "Dataset management");
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Delete a dataset and optionally its extracted memories.
|
|
1289
|
+
*/
|
|
1290
|
+
async deleteDataset(datasetId, deleteMemories = false) {
|
|
1291
|
+
await this.request("DELETE", `/v1/datasets/${datasetId}?delete_memories=${deleteMemories}`, undefined, "Dataset management");
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Get memories extracted from a dataset.
|
|
1295
|
+
*/
|
|
1296
|
+
async getDatasetMemories(datasetId) {
|
|
1297
|
+
return this.request("GET", `/v1/datasets/${datasetId}/memories`, undefined, "Dataset management");
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Query a slice of dataset data using DuckDB.
|
|
1301
|
+
*
|
|
1302
|
+
* Supports both structured filters and raw SQL (SELECT only).
|
|
1303
|
+
* The dataset is queried as a table named 'data'.
|
|
1304
|
+
*/
|
|
1305
|
+
async queryDatasetSlice(datasetId, options = {}) {
|
|
1306
|
+
const body = {
|
|
1307
|
+
limit: options.limit ?? 100,
|
|
1308
|
+
offset: options.offset ?? 0,
|
|
1309
|
+
descending: options.descending ?? false,
|
|
1310
|
+
};
|
|
1311
|
+
if (options.sql !== undefined)
|
|
1312
|
+
body.sql = options.sql;
|
|
1313
|
+
if (options.columns !== undefined)
|
|
1314
|
+
body.columns = options.columns;
|
|
1315
|
+
if (options.filters !== undefined)
|
|
1316
|
+
body.filters = options.filters;
|
|
1317
|
+
if (options.orderBy !== undefined)
|
|
1318
|
+
body.order_by = options.orderBy;
|
|
1319
|
+
return this.request("POST", `/v1/datasets/${datasetId}/slice`, body, "Dataset management");
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Get dataset processing job status.
|
|
1323
|
+
*/
|
|
1324
|
+
async getDatasetJob(jobId) {
|
|
1325
|
+
return this.request("GET", `/v1/datasets/jobs/${jobId}`, undefined, "Dataset processing jobs");
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* List dataset processing jobs in the workspace.
|
|
1329
|
+
*/
|
|
1330
|
+
async listDatasetJobs(options) {
|
|
1331
|
+
const params = new URLSearchParams();
|
|
1332
|
+
if (options?.status)
|
|
1333
|
+
params.set("status", options.status);
|
|
1334
|
+
if (options?.limit !== undefined)
|
|
1335
|
+
params.set("limit", String(options.limit));
|
|
1336
|
+
const query = params.toString();
|
|
1337
|
+
return this.request("GET", `/v1/datasets/jobs${query ? `?${query}` : ""}`, undefined, "Dataset processing jobs");
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Cancel a running dataset processing job.
|
|
1341
|
+
*/
|
|
1342
|
+
async cancelDatasetJob(jobId) {
|
|
1343
|
+
await this.request("POST", `/v1/datasets/jobs/${jobId}/cancel`, undefined, "Dataset processing jobs");
|
|
1344
|
+
}
|
|
1345
|
+
/** Used internally by SkillsNamespace to make requests with OBO authority. */
|
|
1346
|
+
async _skillsRequest(method, path, body, authority, responseMode = "json") {
|
|
1347
|
+
return this.request(method, path, body, undefined, authority, responseMode);
|
|
1348
|
+
}
|
|
1349
|
+
/** Used internally by McpServersNamespace to make requests with OBO authority. */
|
|
1350
|
+
async _mcpServersRequest(method, path, body, authority) {
|
|
1351
|
+
return this.request(method, path, body, undefined, authority);
|
|
1352
|
+
}
|
|
1353
|
+
/** Used internally by KnowledgebaseNamespace to make requests with OBO authority. */
|
|
1354
|
+
async _kbRequest(method, path, body, authority, responseMode = "json") {
|
|
1355
|
+
return this.request(method, path, body, undefined, authority, responseMode);
|
|
1356
|
+
}
|
|
1357
|
+
/** Used internally by RpgNamespace to make requests with OBO authority. */
|
|
1358
|
+
async _rpgRequest(method, path, body, authority) {
|
|
1359
|
+
return this.request(method, path, body, undefined, authority);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Lightweight OBO proxy returned by `client.actingFor()`.
|
|
1364
|
+
* Delegates all calls to the parent client with a fixed AuthorityContext and
|
|
1365
|
+
* optional workspace override. Per-call authority headers are computed fresh
|
|
1366
|
+
* on each request — no shared mutable state, so concurrent calls for different
|
|
1367
|
+
* subjects on the same underlying client never interfere.
|
|
1368
|
+
*/
|
|
1369
|
+
export class OboProxy {
|
|
1370
|
+
_client;
|
|
1371
|
+
_authority;
|
|
1372
|
+
_workspaceId;
|
|
1373
|
+
/** Skills namespace scoped to this proxy's authority. */
|
|
1374
|
+
skills;
|
|
1375
|
+
/** MCP Servers namespace scoped to this proxy's authority. */
|
|
1376
|
+
mcpServers;
|
|
1377
|
+
constructor(client, authority, workspaceId) {
|
|
1378
|
+
this._client = client;
|
|
1379
|
+
this._authority = authority;
|
|
1380
|
+
this._workspaceId = workspaceId;
|
|
1381
|
+
this.skills = new SkillsNamespace(client, authority, workspaceId);
|
|
1382
|
+
this.mcpServers = new McpServersNamespace(client, authority, workspaceId);
|
|
1383
|
+
}
|
|
1384
|
+
/** Further scope this proxy to a single workspace. */
|
|
1385
|
+
forWorkspace(workspaceId) {
|
|
1386
|
+
return new OboProxy(this._client, this._authority, workspaceId);
|
|
1387
|
+
}
|
|
1388
|
+
async remember(content, options = {}) {
|
|
1389
|
+
return this._client.remember(content, {
|
|
1390
|
+
...options,
|
|
1391
|
+
workspaceId: options.workspaceId ?? this._workspaceId,
|
|
1392
|
+
authority: options.authority ?? this._authority,
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
async recall(query, options = {}) {
|
|
1396
|
+
return this._client.recall(query, {
|
|
1397
|
+
...options,
|
|
1398
|
+
workspaceId: options.workspaceId ?? this._workspaceId,
|
|
1399
|
+
authority: options.authority ?? this._authority,
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
async reflect(query, options = {}) {
|
|
1403
|
+
return this._client.reflect(query, {
|
|
1404
|
+
...options,
|
|
1405
|
+
workspaceId: options.workspaceId ?? this._workspaceId,
|
|
1406
|
+
authority: options.authority ?? this._authority,
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
580
1409
|
}
|
|
581
1410
|
//# sourceMappingURL=client.js.map
|