@scitrera/memorylayer-sdk 0.1.22 → 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 +143 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +428 -89
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- 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 +14 -1
- package/dist/mcp_servers.d.ts.map +1 -1
- package/dist/mcp_servers.js +28 -0
- package/dist/mcp_servers.js.map +1 -1
- 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 +85 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +277 -27
- package/dist/skills.js.map +1 -1
- package/dist/types.d.ts +351 -0
- package/dist/types.d.ts.map +1 -1
- 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 +9 -2
- package/vitest.config.ts +0 -12
package/dist/client.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { MemoryLayerError, AuthenticationError, AuthorizationError, NotFoundError, ValidationError, EnterpriseRequiredError } from "./errors.js";
|
|
1
|
+
import { MemoryLayerError, AuthenticationError, AuthorizationError, NotFoundError, ValidationError, EnterpriseRequiredError, RateLimitError } from "./errors.js";
|
|
2
|
+
import { sleep } from "./utils.js";
|
|
2
3
|
import { SkillsNamespace } from "./skills.js";
|
|
3
4
|
import { McpServersNamespace } from "./mcp_servers.js";
|
|
5
|
+
import { KnowledgebaseNamespace } from "./knowledgebase.js";
|
|
6
|
+
import { RpgNamespace } from "./rpg.js";
|
|
4
7
|
export class MemoryLayerClient {
|
|
5
8
|
baseUrl;
|
|
6
9
|
apiKey;
|
|
@@ -8,10 +11,17 @@ export class MemoryLayerClient {
|
|
|
8
11
|
sessionId;
|
|
9
12
|
timeout;
|
|
10
13
|
defaultAuthority;
|
|
14
|
+
fetchImpl;
|
|
15
|
+
maxRetries;
|
|
16
|
+
retryBaseDelay;
|
|
11
17
|
/** Skills namespace — access via `client.skills.list(...)` etc. */
|
|
12
18
|
skills;
|
|
13
19
|
/** MCP Servers namespace — access via `client.mcpServers.list(...)` etc. */
|
|
14
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;
|
|
15
25
|
constructor(config = {}) {
|
|
16
26
|
this.baseUrl = config.baseUrl ?? "http://localhost:61001";
|
|
17
27
|
this.apiKey = config.apiKey;
|
|
@@ -19,8 +29,15 @@ export class MemoryLayerClient {
|
|
|
19
29
|
this.sessionId = config.sessionId;
|
|
20
30
|
this.timeout = config.timeout ?? 30000;
|
|
21
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);
|
|
22
37
|
this.skills = new SkillsNamespace(this);
|
|
23
38
|
this.mcpServers = new McpServersNamespace(this);
|
|
39
|
+
this.kb = new KnowledgebaseNamespace(this);
|
|
40
|
+
this.rpg = new RpgNamespace(this);
|
|
24
41
|
}
|
|
25
42
|
/**
|
|
26
43
|
* Returns a lightweight proxy that sends OBO headers for the given grant/subject
|
|
@@ -62,10 +79,24 @@ export class MemoryLayerClient {
|
|
|
62
79
|
};
|
|
63
80
|
return h;
|
|
64
81
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
}
|
|
69
100
|
if (this.apiKey) {
|
|
70
101
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
71
102
|
}
|
|
@@ -76,30 +107,121 @@ export class MemoryLayerClient {
|
|
|
76
107
|
headers["X-Workspace-ID"] = this.workspaceId;
|
|
77
108
|
}
|
|
78
109
|
Object.assign(headers, this.buildAuthorityHeaders(authority));
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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.
|
|
91
184
|
await this.handleError(response, enterpriseFeature);
|
|
92
185
|
}
|
|
93
|
-
|
|
94
|
-
|
|
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}`);
|
|
95
198
|
}
|
|
96
|
-
return await response.json();
|
|
97
199
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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;
|
|
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();
|
|
102
220
|
}
|
|
221
|
+
if (responseMode === "arraybuffer") {
|
|
222
|
+
return await response.arrayBuffer();
|
|
223
|
+
}
|
|
224
|
+
return await response.json();
|
|
103
225
|
}
|
|
104
226
|
async handleError(response, enterpriseFeature) {
|
|
105
227
|
const body = await response.json().catch(() => ({}));
|
|
@@ -117,6 +239,10 @@ export class MemoryLayerClient {
|
|
|
117
239
|
case 400:
|
|
118
240
|
case 422:
|
|
119
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
|
+
}
|
|
120
246
|
case 501:
|
|
121
247
|
if (enterpriseFeature) {
|
|
122
248
|
throw new EnterpriseRequiredError(enterpriseFeature);
|
|
@@ -138,6 +264,7 @@ export class MemoryLayerClient {
|
|
|
138
264
|
tags: options.tags ?? [],
|
|
139
265
|
metadata: options.metadata ?? {},
|
|
140
266
|
associations: options.associations ?? [],
|
|
267
|
+
relations: options.relations ?? [],
|
|
141
268
|
context_id: options.contextId,
|
|
142
269
|
user_id: options.userId,
|
|
143
270
|
};
|
|
@@ -164,10 +291,19 @@ export class MemoryLayerClient {
|
|
|
164
291
|
max_expansion: options.maxExpansion,
|
|
165
292
|
created_after: options.createdAfter?.toISOString(),
|
|
166
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,
|
|
167
300
|
context: options.conversationContext ?? [],
|
|
168
301
|
rag_threshold: options.ragThreshold,
|
|
169
302
|
detail_level: options.detailLevel,
|
|
170
303
|
user_id: options.userId,
|
|
304
|
+
budget_tokens: options.budgetTokens,
|
|
305
|
+
include_confidence: options.includeConfidence ?? true,
|
|
306
|
+
include_relations: options.includeRelations ?? true,
|
|
171
307
|
};
|
|
172
308
|
return this.request("POST", "/v1/memories/recall", body, undefined, options.authority);
|
|
173
309
|
}
|
|
@@ -211,6 +347,54 @@ export class MemoryLayerClient {
|
|
|
211
347
|
const response = await this.request("GET", `/v1/memories/${memoryId}/associations?direction=${direction}`);
|
|
212
348
|
return response.associations;
|
|
213
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
|
+
}
|
|
214
398
|
// Session operations
|
|
215
399
|
/**
|
|
216
400
|
* Create a new session.
|
|
@@ -257,6 +441,37 @@ export class MemoryLayerClient {
|
|
|
257
441
|
const response = await this.request("GET", `/v1/sessions/${sessionId}`);
|
|
258
442
|
return response.session;
|
|
259
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
|
+
}
|
|
260
475
|
async deleteSession(sessionId) {
|
|
261
476
|
await this.request("DELETE", `/v1/sessions/${sessionId}`);
|
|
262
477
|
}
|
|
@@ -314,8 +529,8 @@ export class MemoryLayerClient {
|
|
|
314
529
|
return response.briefing;
|
|
315
530
|
}
|
|
316
531
|
// Workspace operations
|
|
317
|
-
async createWorkspace(name, settings) {
|
|
318
|
-
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 ?? [] });
|
|
319
534
|
return response.workspace;
|
|
320
535
|
}
|
|
321
536
|
async getWorkspace(workspaceId) {
|
|
@@ -325,8 +540,16 @@ export class MemoryLayerClient {
|
|
|
325
540
|
const response = await this.request("GET", `/v1/workspaces/${id}`);
|
|
326
541
|
return response.workspace;
|
|
327
542
|
}
|
|
328
|
-
async listWorkspaces() {
|
|
329
|
-
|
|
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);
|
|
330
553
|
return response.workspaces;
|
|
331
554
|
}
|
|
332
555
|
async updateWorkspace(workspaceId, updates) {
|
|
@@ -345,6 +568,116 @@ export class MemoryLayerClient {
|
|
|
345
568
|
const response = await this.request("GET", `/v1/workspaces/${this.workspaceId}/contexts`);
|
|
346
569
|
return response.contexts;
|
|
347
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
|
+
}
|
|
348
681
|
// Batch operations
|
|
349
682
|
async batchMemories(operations) {
|
|
350
683
|
return this.request("POST", "/v1/memories/batch", { operations });
|
|
@@ -401,20 +734,11 @@ export class MemoryLayerClient {
|
|
|
401
734
|
const query = params.toString() ? `?${params.toString()}` : '';
|
|
402
735
|
// Fetch NDJSON response
|
|
403
736
|
const url = `${this.baseUrl}/v1/workspaces/${id}/export${query}`;
|
|
404
|
-
const headers =
|
|
405
|
-
if (this.apiKey) {
|
|
406
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
407
|
-
}
|
|
408
|
-
if (this.sessionId) {
|
|
409
|
-
headers["X-Session-ID"] = this.sessionId;
|
|
410
|
-
}
|
|
411
|
-
if (this.workspaceId) {
|
|
412
|
-
headers["X-Workspace-ID"] = this.workspaceId;
|
|
413
|
-
}
|
|
737
|
+
const headers = this.buildHeaders(undefined, false);
|
|
414
738
|
const controller = new AbortController();
|
|
415
739
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
416
740
|
try {
|
|
417
|
-
const response = await
|
|
741
|
+
const response = await this.fetchImpl(url, {
|
|
418
742
|
method: "GET",
|
|
419
743
|
headers,
|
|
420
744
|
signal: controller.signal,
|
|
@@ -474,20 +798,11 @@ export class MemoryLayerClient {
|
|
|
474
798
|
}
|
|
475
799
|
const query = params.toString() ? `?${params.toString()}` : '';
|
|
476
800
|
const url = `${this.baseUrl}/v1/workspaces/${id}/export${query}`;
|
|
477
|
-
const headers =
|
|
478
|
-
if (this.apiKey) {
|
|
479
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
480
|
-
}
|
|
481
|
-
if (this.sessionId) {
|
|
482
|
-
headers["X-Session-ID"] = this.sessionId;
|
|
483
|
-
}
|
|
484
|
-
if (this.workspaceId) {
|
|
485
|
-
headers["X-Workspace-ID"] = this.workspaceId;
|
|
486
|
-
}
|
|
801
|
+
const headers = this.buildHeaders(undefined, false);
|
|
487
802
|
const controller = new AbortController();
|
|
488
803
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
489
804
|
try {
|
|
490
|
-
const response = await
|
|
805
|
+
const response = await this.fetchImpl(url, {
|
|
491
806
|
method: "GET",
|
|
492
807
|
headers,
|
|
493
808
|
signal: controller.signal,
|
|
@@ -514,22 +829,12 @@ export class MemoryLayerClient {
|
|
|
514
829
|
}
|
|
515
830
|
async importWorkspaceStream(workspaceId, ndjsonBody) {
|
|
516
831
|
const url = `${this.baseUrl}/v1/workspaces/${workspaceId}/import`;
|
|
517
|
-
const headers =
|
|
518
|
-
|
|
519
|
-
};
|
|
520
|
-
if (this.apiKey) {
|
|
521
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
522
|
-
}
|
|
523
|
-
if (this.sessionId) {
|
|
524
|
-
headers["X-Session-ID"] = this.sessionId;
|
|
525
|
-
}
|
|
526
|
-
if (this.workspaceId) {
|
|
527
|
-
headers["X-Workspace-ID"] = this.workspaceId;
|
|
528
|
-
}
|
|
832
|
+
const headers = this.buildHeaders(undefined, false);
|
|
833
|
+
headers["Content-Type"] = "application/x-ndjson";
|
|
529
834
|
const controller = new AbortController();
|
|
530
835
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
531
836
|
try {
|
|
532
|
-
const response = await
|
|
837
|
+
const response = await this.fetchImpl(url, {
|
|
533
838
|
method: "POST",
|
|
534
839
|
headers,
|
|
535
840
|
body: ndjsonBody,
|
|
@@ -640,18 +945,12 @@ export class MemoryLayerClient {
|
|
|
640
945
|
formData.append("importance", String(options.importance));
|
|
641
946
|
if (options.retainOriginal !== undefined)
|
|
642
947
|
formData.append("retain_original", String(options.retainOriginal));
|
|
643
|
-
const headers =
|
|
644
|
-
if (this.apiKey)
|
|
645
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
646
|
-
if (this.sessionId)
|
|
647
|
-
headers["X-Session-ID"] = this.sessionId;
|
|
648
|
-
if (this.workspaceId)
|
|
649
|
-
headers["X-Workspace-ID"] = this.workspaceId;
|
|
948
|
+
const headers = this.buildHeaders(undefined, false);
|
|
650
949
|
const url = `${this.baseUrl}/v1/documents`;
|
|
651
950
|
const controller = new AbortController();
|
|
652
951
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
653
952
|
try {
|
|
654
|
-
const response = await
|
|
953
|
+
const response = await this.fetchImpl(url, {
|
|
655
954
|
method: "POST",
|
|
656
955
|
headers,
|
|
657
956
|
body: formData,
|
|
@@ -719,18 +1018,12 @@ export class MemoryLayerClient {
|
|
|
719
1018
|
* Get a page image as a Blob.
|
|
720
1019
|
*/
|
|
721
1020
|
async getPageImage(documentId, pageId) {
|
|
722
|
-
const headers =
|
|
723
|
-
if (this.apiKey)
|
|
724
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
725
|
-
if (this.sessionId)
|
|
726
|
-
headers["X-Session-ID"] = this.sessionId;
|
|
727
|
-
if (this.workspaceId)
|
|
728
|
-
headers["X-Workspace-ID"] = this.workspaceId;
|
|
1021
|
+
const headers = this.buildHeaders(undefined, false);
|
|
729
1022
|
const url = `${this.baseUrl}/v1/documents/${documentId}/pages/${pageId}/image`;
|
|
730
1023
|
const controller = new AbortController();
|
|
731
1024
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
732
1025
|
try {
|
|
733
|
-
const response = await
|
|
1026
|
+
const response = await this.fetchImpl(url, { method: "GET", headers, signal: controller.signal });
|
|
734
1027
|
clearTimeout(timeoutId);
|
|
735
1028
|
if (!response.ok) {
|
|
736
1029
|
await this.handleError(response, "Document page images");
|
|
@@ -848,6 +1141,50 @@ export class MemoryLayerClient {
|
|
|
848
1141
|
const query = params.toString();
|
|
849
1142
|
await this.request("DELETE", `/v1/threads/${threadId}${query ? `?${query}` : ""}`);
|
|
850
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
|
+
}
|
|
851
1188
|
async appendMessages(threadId, messages, workspaceId) {
|
|
852
1189
|
const params = new URLSearchParams();
|
|
853
1190
|
const wsId = workspaceId ?? this.workspaceId;
|
|
@@ -904,18 +1241,12 @@ export class MemoryLayerClient {
|
|
|
904
1241
|
formData.append("detect_time_series", String(options.detectTimeSeries));
|
|
905
1242
|
if (options.generateSummaries !== undefined)
|
|
906
1243
|
formData.append("generate_summaries", String(options.generateSummaries));
|
|
907
|
-
const headers =
|
|
908
|
-
if (this.apiKey)
|
|
909
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
910
|
-
if (this.sessionId)
|
|
911
|
-
headers["X-Session-ID"] = this.sessionId;
|
|
912
|
-
if (this.workspaceId)
|
|
913
|
-
headers["X-Workspace-ID"] = this.workspaceId;
|
|
1244
|
+
const headers = this.buildHeaders(undefined, false);
|
|
914
1245
|
const url = `${this.baseUrl}/v1/datasets`;
|
|
915
1246
|
const controller = new AbortController();
|
|
916
1247
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
917
1248
|
try {
|
|
918
|
-
const response = await
|
|
1249
|
+
const response = await this.fetchImpl(url, {
|
|
919
1250
|
method: "POST",
|
|
920
1251
|
headers,
|
|
921
1252
|
body: formData,
|
|
@@ -1012,13 +1343,21 @@ export class MemoryLayerClient {
|
|
|
1012
1343
|
await this.request("POST", `/v1/datasets/jobs/${jobId}/cancel`, undefined, "Dataset processing jobs");
|
|
1013
1344
|
}
|
|
1014
1345
|
/** Used internally by SkillsNamespace to make requests with OBO authority. */
|
|
1015
|
-
async _skillsRequest(method, path, body, authority) {
|
|
1016
|
-
return this.request(method, path, body, undefined, authority);
|
|
1346
|
+
async _skillsRequest(method, path, body, authority, responseMode = "json") {
|
|
1347
|
+
return this.request(method, path, body, undefined, authority, responseMode);
|
|
1017
1348
|
}
|
|
1018
1349
|
/** Used internally by McpServersNamespace to make requests with OBO authority. */
|
|
1019
1350
|
async _mcpServersRequest(method, path, body, authority) {
|
|
1020
1351
|
return this.request(method, path, body, undefined, authority);
|
|
1021
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
|
+
}
|
|
1022
1361
|
}
|
|
1023
1362
|
/**
|
|
1024
1363
|
* Lightweight OBO proxy returned by `client.actingFor()`.
|