@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/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
- async request(method, path, body) {
36
- const headers = {
37
- "Content-Type": "application/json",
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
- const url = `${this.baseUrl}${path}`;
49
- const controller = new AbortController();
50
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
51
- try {
52
- const response = await fetch(url, {
53
- method,
54
- headers,
55
- body: body ? JSON.stringify(body) : undefined,
56
- signal: controller.signal,
57
- });
58
- clearTimeout(timeoutId);
59
- if (!response.ok) {
60
- await this.handleError(response);
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
- if (response.status === 204) {
63
- return undefined;
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
- catch (error) {
68
- if (error instanceof MemoryLayerError)
69
- throw error;
70
- throw new MemoryLayerError(`Request failed: ${error}`);
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
- const response = await this.request("GET", "/v1/workspaces");
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 fetch(url, {
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 fetch(url, {
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
- "Content-Type": "application/x-ndjson",
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 fetch(url, {
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