memorysync-sdk 1.1.0 → 1.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/index.d.mts +303 -1
- package/dist/index.d.ts +303 -1
- package/dist/index.js +450 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +450 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +49 -48
package/dist/index.mjs
CHANGED
|
@@ -41,7 +41,7 @@ var ServerError = class extends MemorySyncError {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
// src/control-plane.ts
|
|
44
|
-
var SDK_VERSION = "1.1.
|
|
44
|
+
var SDK_VERSION = "1.1.1";
|
|
45
45
|
function safeJson(text) {
|
|
46
46
|
try {
|
|
47
47
|
return JSON.parse(text);
|
|
@@ -111,7 +111,22 @@ function positiveId(value, name) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
function nonEmpty(value, name) {
|
|
114
|
-
if (!value.trim()) throw new ValidationError(`${name} must not be empty`);
|
|
114
|
+
if (typeof value !== "string" || !value.trim()) throw new ValidationError(`${name} must not be empty`);
|
|
115
|
+
}
|
|
116
|
+
function boundedInteger(value, name, minimum, maximum) {
|
|
117
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
118
|
+
throw new ValidationError(`${name} must be an integer between ${minimum} and ${maximum}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function nonNegativeInteger(value, name) {
|
|
122
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
123
|
+
throw new ValidationError(`${name} must be a non-negative integer`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function nonEmptyStrings(values, name) {
|
|
127
|
+
if (!Array.isArray(values) || values.length === 0 || values.some((value) => typeof value !== "string" || !value.trim())) {
|
|
128
|
+
throw new ValidationError(`${name} must contain at least one non-empty string`);
|
|
129
|
+
}
|
|
115
130
|
}
|
|
116
131
|
function webhookRetryConfig(config) {
|
|
117
132
|
const wire = {};
|
|
@@ -134,9 +149,11 @@ function webhookSignatureConfig(config) {
|
|
|
134
149
|
function validateWebhook(name, url, events) {
|
|
135
150
|
nonEmpty(name, "name");
|
|
136
151
|
if (name.length > 128) throw new ValidationError("name may contain at most 128 characters");
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
152
|
+
nonEmptyStrings(events, "events");
|
|
153
|
+
validateWebhookUrl(url);
|
|
154
|
+
}
|
|
155
|
+
function validateWebhookUrl(url) {
|
|
156
|
+
nonEmpty(url, "url");
|
|
140
157
|
let parsed;
|
|
141
158
|
try {
|
|
142
159
|
parsed = new URL(url);
|
|
@@ -290,10 +307,11 @@ var ControlPlaneClient = class {
|
|
|
290
307
|
return this.request("POST", `/auth/sessions/${sessionId}/revoke`, options);
|
|
291
308
|
}
|
|
292
309
|
async listAuditEvents(query = {}, options = {}) {
|
|
293
|
-
if (query.limit !== void 0)
|
|
294
|
-
if (query.cursor !== void 0)
|
|
295
|
-
if (query.skip !== void 0
|
|
296
|
-
|
|
310
|
+
if (query.limit !== void 0) boundedInteger(query.limit, "limit", 1, 200);
|
|
311
|
+
if (query.cursor !== void 0) nonNegativeInteger(query.cursor, "cursor");
|
|
312
|
+
if (query.skip !== void 0) nonNegativeInteger(query.skip, "skip");
|
|
313
|
+
if (query.sortDirection !== void 0 && query.sortDirection !== "asc" && query.sortDirection !== "desc") {
|
|
314
|
+
throw new ValidationError("sortDirection must be 'asc' or 'desc'");
|
|
297
315
|
}
|
|
298
316
|
const path = "/admin/audit-logs" + queryString({
|
|
299
317
|
limit: query.limit,
|
|
@@ -314,7 +332,8 @@ var ControlPlaneClient = class {
|
|
|
314
332
|
success: query.success,
|
|
315
333
|
source: query.source,
|
|
316
334
|
ingest_method: query.ingestMethod,
|
|
317
|
-
search: query.search
|
|
335
|
+
search: query.search,
|
|
336
|
+
include_stats: query.includeStats
|
|
318
337
|
});
|
|
319
338
|
const raw = await this.request("GET", path, options);
|
|
320
339
|
return {
|
|
@@ -325,6 +344,7 @@ var ControlPlaneClient = class {
|
|
|
325
344
|
};
|
|
326
345
|
}
|
|
327
346
|
async listIntegrations(query = {}, options = {}) {
|
|
347
|
+
if (query.category !== void 0) nonEmpty(query.category, "category");
|
|
328
348
|
const path = "/api/v1/integrations/catalog" + queryString({ category: query.category });
|
|
329
349
|
return this.request("GET", path, options);
|
|
330
350
|
}
|
|
@@ -355,12 +375,20 @@ var ControlPlaneClient = class {
|
|
|
355
375
|
throw new ValidationError("description may contain at most 500 characters");
|
|
356
376
|
}
|
|
357
377
|
if (request.projectId !== void 0) nonEmpty(request.projectId, "projectId");
|
|
378
|
+
if (options.projectId !== void 0) nonEmpty(options.projectId, "projectId override");
|
|
379
|
+
if (request.projectId && options.projectId && request.projectId.trim() !== options.projectId.trim()) {
|
|
380
|
+
throw new ValidationError("request projectId and options projectId must match");
|
|
381
|
+
}
|
|
358
382
|
const body = { name: request.name, url: request.url, events: request.events };
|
|
359
383
|
if (request.description !== void 0) body.description = request.description;
|
|
360
384
|
if (request.retryConfig !== void 0) body.retry_config = webhookRetryConfig(request.retryConfig);
|
|
361
385
|
if (request.signatureConfig !== void 0) body.signature_config = webhookSignatureConfig(request.signatureConfig);
|
|
362
386
|
if (request.projectId !== void 0) body.project_id = request.projectId;
|
|
363
|
-
return this.request("POST", "/org/webhooks", {
|
|
387
|
+
return this.request("POST", "/org/webhooks", {
|
|
388
|
+
...options,
|
|
389
|
+
projectId: options.projectId ?? request.projectId,
|
|
390
|
+
body
|
|
391
|
+
});
|
|
364
392
|
}
|
|
365
393
|
async listWebhooks(options = {}) {
|
|
366
394
|
return this.request("GET", "/org/webhooks", options);
|
|
@@ -374,7 +402,7 @@ var ControlPlaneClient = class {
|
|
|
374
402
|
body.name = request.name;
|
|
375
403
|
}
|
|
376
404
|
if (request.url !== void 0) {
|
|
377
|
-
|
|
405
|
+
validateWebhookUrl(request.url);
|
|
378
406
|
body.url = request.url;
|
|
379
407
|
}
|
|
380
408
|
if (request.description !== void 0) {
|
|
@@ -384,9 +412,7 @@ var ControlPlaneClient = class {
|
|
|
384
412
|
body.description = request.description;
|
|
385
413
|
}
|
|
386
414
|
if (request.events !== void 0) {
|
|
387
|
-
|
|
388
|
-
throw new ValidationError("events must contain at least one non-empty event type");
|
|
389
|
-
}
|
|
415
|
+
nonEmptyStrings(request.events, "events");
|
|
390
416
|
body.events = request.events;
|
|
391
417
|
}
|
|
392
418
|
if (request.retryConfig !== void 0) body.retry_config = webhookRetryConfig(request.retryConfig);
|
|
@@ -417,9 +443,7 @@ var ControlPlaneClient = class {
|
|
|
417
443
|
if (request.limit !== void 0 && (!Number.isInteger(request.limit) || request.limit < 1 || request.limit > 1e3)) {
|
|
418
444
|
throw new ValidationError("limit must be an integer between 1 and 1000");
|
|
419
445
|
}
|
|
420
|
-
if (request.statuses !== void 0
|
|
421
|
-
throw new ValidationError("statuses must contain at least one non-empty status");
|
|
422
|
-
}
|
|
446
|
+
if (request.statuses !== void 0) nonEmptyStrings(request.statuses, "statuses");
|
|
423
447
|
const body = {};
|
|
424
448
|
if (request.sinceMinutes !== void 0) body.since_minutes = request.sinceMinutes;
|
|
425
449
|
if (request.statuses !== void 0) body.statuses = request.statuses;
|
|
@@ -443,7 +467,7 @@ var ControlPlaneClient = class {
|
|
|
443
467
|
};
|
|
444
468
|
|
|
445
469
|
// src/index.ts
|
|
446
|
-
var SDK_VERSION2 = "1.
|
|
470
|
+
var SDK_VERSION2 = "1.2.0";
|
|
447
471
|
function camelToSnakeKey(key) {
|
|
448
472
|
return key.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
449
473
|
}
|
|
@@ -472,6 +496,22 @@ function snakeToCamelMemory(m) {
|
|
|
472
496
|
score: m.score ?? null
|
|
473
497
|
};
|
|
474
498
|
}
|
|
499
|
+
function buildQuery(params) {
|
|
500
|
+
if (!params) return "";
|
|
501
|
+
const search = new URLSearchParams();
|
|
502
|
+
for (const [key, value] of Object.entries(params)) {
|
|
503
|
+
if (value === void 0 || value === null) continue;
|
|
504
|
+
if (Array.isArray(value)) {
|
|
505
|
+
for (const item of value) {
|
|
506
|
+
if (item !== void 0 && item !== null) search.append(key, String(item));
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
search.append(key, String(value));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const qs = search.toString();
|
|
513
|
+
return qs ? `?${qs}` : "";
|
|
514
|
+
}
|
|
475
515
|
function safeJson2(text) {
|
|
476
516
|
try {
|
|
477
517
|
return JSON.parse(text);
|
|
@@ -531,17 +571,20 @@ var MemorySyncClient = class {
|
|
|
531
571
|
return h;
|
|
532
572
|
}
|
|
533
573
|
async request(method, path, options = {}) {
|
|
534
|
-
const url = `${this.baseUrl}${path}`;
|
|
574
|
+
const url = `${this.baseUrl}${path}${buildQuery(options.query)}`;
|
|
535
575
|
const controller = new AbortController();
|
|
536
576
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
537
577
|
try {
|
|
538
578
|
const headers = this.headers(
|
|
539
579
|
options.endUserOverride ? { "X-End-User-ID": options.endUserOverride } : {}
|
|
540
580
|
);
|
|
581
|
+
if (options.form) {
|
|
582
|
+
delete headers["Content-Type"];
|
|
583
|
+
}
|
|
541
584
|
const res = await this.fetchImpl(url, {
|
|
542
585
|
method,
|
|
543
586
|
headers,
|
|
544
|
-
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
|
|
587
|
+
body: options.form ? options.form : options.body !== void 0 ? JSON.stringify(options.body) : void 0,
|
|
545
588
|
signal: controller.signal
|
|
546
589
|
});
|
|
547
590
|
const requestId = res.headers.get("X-Request-ID") ?? void 0;
|
|
@@ -668,14 +711,51 @@ var MemorySyncClient = class {
|
|
|
668
711
|
const raw = await this.request("PATCH", `/memory/${memoryId}`, { body });
|
|
669
712
|
return snakeToCamelMemory(raw);
|
|
670
713
|
}
|
|
671
|
-
async forget(
|
|
672
|
-
|
|
673
|
-
|
|
714
|
+
async forget(arg, legacyReason) {
|
|
715
|
+
const req = Array.isArray(arg) ? { memoryIds: arg, reason: legacyReason } : arg;
|
|
716
|
+
const hasIds = req.memoryIds !== void 0;
|
|
717
|
+
const hasFilters = req.filters !== void 0;
|
|
718
|
+
if (hasIds && hasFilters) {
|
|
719
|
+
throw new ValidationError("Provide either memoryIds or filters, not both");
|
|
720
|
+
}
|
|
721
|
+
if (!hasIds && !hasFilters) {
|
|
722
|
+
throw new ValidationError("Provide either memoryIds or filters");
|
|
723
|
+
}
|
|
724
|
+
const body = {};
|
|
725
|
+
if (hasIds) {
|
|
726
|
+
if (!Array.isArray(req.memoryIds) || req.memoryIds.length === 0) {
|
|
727
|
+
throw new ValidationError("memoryIds must be a non-empty array");
|
|
728
|
+
}
|
|
729
|
+
body.memory_ids = req.memoryIds;
|
|
730
|
+
} else {
|
|
731
|
+
const f = req.filters;
|
|
732
|
+
const filters = {};
|
|
733
|
+
if (f.source !== void 0) filters.source = f.source;
|
|
734
|
+
if (f.eventType !== void 0) filters.event_type = f.eventType;
|
|
735
|
+
if (f.tags !== void 0) filters.tags = f.tags;
|
|
736
|
+
if (f.tier !== void 0) filters.tier = f.tier;
|
|
737
|
+
if (f.before !== void 0) filters.before = f.before;
|
|
738
|
+
if (f.after !== void 0) filters.after = f.after;
|
|
739
|
+
if (Object.keys(filters).length === 0) {
|
|
740
|
+
throw new ValidationError(
|
|
741
|
+
"filters must set at least one criterion; use purgeUser() to remove everything for an end user"
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
body.filters = filters;
|
|
745
|
+
if (req.dryRun) body.dry_run = true;
|
|
674
746
|
}
|
|
675
|
-
|
|
676
|
-
if (reason !== void 0) body.reason = reason;
|
|
747
|
+
if (req.reason !== void 0) body.reason = req.reason;
|
|
677
748
|
return await this.request("DELETE", "/memory/forget", { body });
|
|
678
749
|
}
|
|
750
|
+
/**
|
|
751
|
+
* Delete every memory belonging to the calling end user.
|
|
752
|
+
*
|
|
753
|
+
* Separate from {@link forget} on purpose: this reads like what it does, so a
|
|
754
|
+
* whole-namespace delete can never be the accidental result of an empty filter.
|
|
755
|
+
*/
|
|
756
|
+
async purgeUser() {
|
|
757
|
+
return await this.request("DELETE", "/memory/user/purge") ?? {};
|
|
758
|
+
}
|
|
679
759
|
async summarize(req) {
|
|
680
760
|
if (!req.memoryIds || req.memoryIds.length === 0) {
|
|
681
761
|
throw new ValidationError("summarize() requires memoryIds");
|
|
@@ -733,7 +813,351 @@ var MemorySyncClient = class {
|
|
|
733
813
|
createdAt: raw.created_at
|
|
734
814
|
};
|
|
735
815
|
}
|
|
816
|
+
// ── Files ──────────────────────────────────────────────────────────
|
|
817
|
+
/**
|
|
818
|
+
* Ingest a document and store the memories extracted from its text.
|
|
819
|
+
*
|
|
820
|
+
* Accepts the formats the connectors accept — PDF, DOCX, PPTX, XLSX, CSV,
|
|
821
|
+
* text, Markdown, HTML, source code, and images/audio/video where
|
|
822
|
+
* transcription is configured.
|
|
823
|
+
*
|
|
824
|
+
* Billed as an add, one unit per memory created. Resolves to the first stored
|
|
825
|
+
* memory, or an {@link AddSkippedResponse} when the file yielded nothing worth
|
|
826
|
+
* keeping — a blank scan, a sheet of empty cells, or content the extractor
|
|
827
|
+
* judges trivial are all normal outcomes rather than errors.
|
|
828
|
+
*/
|
|
829
|
+
async upload(req) {
|
|
830
|
+
if (!req.filename?.trim()) {
|
|
831
|
+
throw new ValidationError("filename is required so the server can pick a parser");
|
|
832
|
+
}
|
|
833
|
+
const form = new FormData();
|
|
834
|
+
const blob = req.file instanceof Uint8Array ? new Blob([req.file], {
|
|
835
|
+
type: req.contentType ?? "application/octet-stream"
|
|
836
|
+
}) : req.file;
|
|
837
|
+
form.append("file", blob, req.filename);
|
|
838
|
+
if (req.source !== void 0) form.append("source", req.source);
|
|
839
|
+
if (req.metadata !== void 0) form.append("metadata", JSON.stringify(req.metadata));
|
|
840
|
+
if (req.endUserId !== void 0) form.append("end_user_id", req.endUserId);
|
|
841
|
+
const raw = await this.request("POST", "/memory/upload", {
|
|
842
|
+
form,
|
|
843
|
+
endUserOverride: req.endUserId
|
|
844
|
+
});
|
|
845
|
+
if (raw && raw.status === "skipped") {
|
|
846
|
+
return {
|
|
847
|
+
status: "skipped",
|
|
848
|
+
reason: raw.reason ?? "no_extractable_text",
|
|
849
|
+
memoryIds: raw.memory_ids ?? [],
|
|
850
|
+
candidatesExtracted: raw.candidates_extracted ?? 0,
|
|
851
|
+
candidatesStored: raw.candidates_stored ?? 0
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
return snakeToCamelMemory(raw);
|
|
855
|
+
}
|
|
856
|
+
// ── Bulk edit ──────────────────────────────────────────────────────
|
|
857
|
+
/**
|
|
858
|
+
* Apply many metadata edits in one request.
|
|
859
|
+
*
|
|
860
|
+
* Editable: `tags`, `importance`, `metadata`, `source`, `eventType`. A memory's
|
|
861
|
+
* text, embeddings, owner, environment and project are not editable.
|
|
862
|
+
*
|
|
863
|
+
* Applied in one transaction, so the batch either lands or it does not — but an
|
|
864
|
+
* id the caller cannot see is reported per item rather than failing the request.
|
|
865
|
+
*/
|
|
866
|
+
async batchUpdate(items) {
|
|
867
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
868
|
+
throw new ValidationError("items must contain at least one entry");
|
|
869
|
+
}
|
|
870
|
+
if (items.length > 100) {
|
|
871
|
+
throw new ValidationError("items may contain at most 100 entries per request");
|
|
872
|
+
}
|
|
873
|
+
const seen = /* @__PURE__ */ new Map();
|
|
874
|
+
const payload = items.map((item, index) => {
|
|
875
|
+
if (!Number.isInteger(item.memoryId) || item.memoryId <= 0) {
|
|
876
|
+
throw new ValidationError(`items[${index}].memoryId must be a positive integer`);
|
|
877
|
+
}
|
|
878
|
+
const o = { memory_id: item.memoryId };
|
|
879
|
+
if (item.tags !== void 0) o.tags = item.tags;
|
|
880
|
+
if (item.importance !== void 0) o.importance = item.importance;
|
|
881
|
+
if (item.metadata !== void 0) o.metadata = item.metadata;
|
|
882
|
+
if (item.source !== void 0) o.source = item.source;
|
|
883
|
+
if (item.eventType !== void 0) o.event_type = item.eventType;
|
|
884
|
+
if (Object.keys(o).length === 1) {
|
|
885
|
+
throw new ValidationError(
|
|
886
|
+
`items[${index}] (memoryId ${item.memoryId}): at least one updatable field must be provided`
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
const previous = seen.get(item.memoryId);
|
|
890
|
+
if (previous !== void 0) {
|
|
891
|
+
throw new ValidationError(
|
|
892
|
+
`items must not contain the same memoryId twice: ${item.memoryId} appears at index ${previous} and ${index}`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
seen.set(item.memoryId, index);
|
|
896
|
+
return o;
|
|
897
|
+
});
|
|
898
|
+
const raw = await this.request("POST", "/memory/batch-update", {
|
|
899
|
+
body: { items: payload }
|
|
900
|
+
});
|
|
901
|
+
return {
|
|
902
|
+
total: raw?.total ?? 0,
|
|
903
|
+
updated: raw?.updated ?? 0,
|
|
904
|
+
notFound: raw?.not_found ?? 0,
|
|
905
|
+
results: (raw?.results ?? []).map((r) => ({
|
|
906
|
+
index: r.index,
|
|
907
|
+
memoryId: r.memory_id,
|
|
908
|
+
status: r.status,
|
|
909
|
+
changedFields: r.changed_fields ?? []
|
|
910
|
+
}))
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
// ── History and feedback ───────────────────────────────────────────
|
|
914
|
+
/**
|
|
915
|
+
* Recorded changes to one memory, oldest first.
|
|
916
|
+
*
|
|
917
|
+
* Entry 0 is the creation. Later entries carry the old and new value per field.
|
|
918
|
+
* Entries written by background workers have `actor: null`. Only
|
|
919
|
+
* user-meaningful fields are tracked; the watched list comes back in
|
|
920
|
+
* `trackedFields`.
|
|
921
|
+
*/
|
|
922
|
+
async history(memoryId, opts = {}) {
|
|
923
|
+
if (!Number.isInteger(memoryId) || memoryId <= 0) {
|
|
924
|
+
throw new ValidationError("memoryId must be a positive integer");
|
|
925
|
+
}
|
|
926
|
+
const raw = await this.request(
|
|
927
|
+
"GET",
|
|
928
|
+
`/memory/${memoryId}/history`,
|
|
929
|
+
{ query: { limit: opts.limit ?? 100, offset: opts.offset ?? 0 } }
|
|
930
|
+
);
|
|
931
|
+
return {
|
|
932
|
+
memoryId: raw?.memory_id ?? memoryId,
|
|
933
|
+
total: raw?.total ?? 0,
|
|
934
|
+
revisions: (raw?.revisions ?? []).map((r) => ({
|
|
935
|
+
revision: r.revision,
|
|
936
|
+
event: r.event,
|
|
937
|
+
changedFields: r.changed_fields ?? [],
|
|
938
|
+
diff: r.diff ?? {},
|
|
939
|
+
actor: r.actor ?? null,
|
|
940
|
+
createdAt: r.created_at
|
|
941
|
+
})),
|
|
942
|
+
trackedFields: raw?.tracked_fields ?? []
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Tell MemorySync whether a memory was useful.
|
|
947
|
+
*
|
|
948
|
+
* By default this moves the memory's `importance`, a weighted retrieval-ranking
|
|
949
|
+
* factor, so a memory marked useful surfaces more readily and one marked wrong
|
|
950
|
+
* surfaces less. The size of the move is adaptive: consistent signals amplify
|
|
951
|
+
* it, mixed signals damp it. Importance is clamped to [0.05, 1.0], so no run of
|
|
952
|
+
* negative feedback can make a memory permanently unreachable. Not billed.
|
|
953
|
+
*/
|
|
954
|
+
async feedback(memoryId, signal, opts = {}) {
|
|
955
|
+
if (!Number.isInteger(memoryId) || memoryId <= 0) {
|
|
956
|
+
throw new ValidationError("memoryId must be a positive integer");
|
|
957
|
+
}
|
|
958
|
+
const valid = ["positive", "negative", "retrieved", "ignored"];
|
|
959
|
+
if (!valid.includes(signal)) {
|
|
960
|
+
throw new ValidationError(`signal must be one of ${valid.join(", ")}; got ${String(signal)}`);
|
|
961
|
+
}
|
|
962
|
+
const body = { signal };
|
|
963
|
+
if (opts.comment !== void 0) body.comment = opts.comment;
|
|
964
|
+
const raw = await this.request(
|
|
965
|
+
"POST",
|
|
966
|
+
`/memory/${memoryId}/feedback`,
|
|
967
|
+
{ body }
|
|
968
|
+
);
|
|
969
|
+
const summary = raw?.summary ?? {};
|
|
970
|
+
const trend = summary.trend ?? {};
|
|
971
|
+
return {
|
|
972
|
+
memoryId: raw?.memory_id ?? memoryId,
|
|
973
|
+
signal: raw?.signal ?? signal,
|
|
974
|
+
importanceBefore: raw?.importance_before ?? 0,
|
|
975
|
+
importanceAfter: raw?.importance_after ?? 0,
|
|
976
|
+
adjustment: raw?.adjustment ?? 0,
|
|
977
|
+
influencedRanking: Boolean(raw?.influenced_ranking),
|
|
978
|
+
summary: {
|
|
979
|
+
totalSignals: summary.total_signals ?? 0,
|
|
980
|
+
signalCounts: summary.signal_counts ?? {},
|
|
981
|
+
trend: {
|
|
982
|
+
momentum: trend.momentum ?? "neutral",
|
|
983
|
+
consistency: trend.consistency ?? 0,
|
|
984
|
+
trendMultiplier: trend.trend_multiplier ?? 1,
|
|
985
|
+
recentCount: trend.recent_count ?? 0
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
// ── Ontology ───────────────────────────────────────────────────────
|
|
991
|
+
/** The memory vocabulary in effect for this organization. */
|
|
992
|
+
async getOntology() {
|
|
993
|
+
const raw = await this.request("GET", "/memory/ontology");
|
|
994
|
+
return toOntology(raw);
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Replace this organization's *additions* to the vocabulary.
|
|
998
|
+
*
|
|
999
|
+
* The two vocabularies are independent: omit one and it is left untouched, so
|
|
1000
|
+
* adding a content type cannot wipe your relation types. Pass an empty array to
|
|
1001
|
+
* clear a vocabulary's custom entries. The built-in types always remain.
|
|
1002
|
+
*/
|
|
1003
|
+
async updateOntology(req) {
|
|
1004
|
+
if (req.contentTypes === void 0 && req.relationTypes === void 0) {
|
|
1005
|
+
throw new ValidationError(
|
|
1006
|
+
"provide contentTypes, relationTypes, or both; an empty request would silently do nothing"
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
const body = {};
|
|
1010
|
+
if (req.contentTypes !== void 0) body.content_types = req.contentTypes;
|
|
1011
|
+
if (req.relationTypes !== void 0) body.relation_types = req.relationTypes;
|
|
1012
|
+
const raw = await this.request("PUT", "/memory/ontology", { body });
|
|
1013
|
+
return toOntology(raw);
|
|
1014
|
+
}
|
|
1015
|
+
// ── Retrieval variants ─────────────────────────────────────────────
|
|
1016
|
+
/**
|
|
1017
|
+
* Alias of {@link query} against `/memory/retrieve`.
|
|
1018
|
+
*
|
|
1019
|
+
* Both paths are live, and integrators arriving from other platforms reach for
|
|
1020
|
+
* `retrieve`. Identical semantics.
|
|
1021
|
+
*/
|
|
1022
|
+
async retrieve(req) {
|
|
1023
|
+
const body = { query: req.query };
|
|
1024
|
+
if (req.k !== void 0) body.k = req.k;
|
|
1025
|
+
if (req.filters !== void 0) body.filters = camelToSnakeShallow(req.filters);
|
|
1026
|
+
if (req.sessionId !== void 0) body.session_id = req.sessionId;
|
|
1027
|
+
if (req.traversalDepth !== void 0) body.traversal_depth = req.traversalDepth;
|
|
1028
|
+
const raw = await this.request("POST", "/memory/retrieve", { body });
|
|
1029
|
+
return {
|
|
1030
|
+
memories: (raw.memories ?? []).map(snakeToCamelMemory),
|
|
1031
|
+
context: raw.context ?? null,
|
|
1032
|
+
latencyMs: raw.latency_ms ?? null,
|
|
1033
|
+
sessionId: raw.session_id ?? null,
|
|
1034
|
+
queryIntent: raw.query_intent ?? null
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Route a question to the best knowledge source and answer from it.
|
|
1039
|
+
*
|
|
1040
|
+
* Returns the raw payload: the response carries routing diagnostics whose shape
|
|
1041
|
+
* is richer and more volatile than an SDK should freeze into an interface.
|
|
1042
|
+
*/
|
|
1043
|
+
async searchRouted(query, opts = {}) {
|
|
1044
|
+
const body = { query };
|
|
1045
|
+
if (opts.k !== void 0) body.k = opts.k;
|
|
1046
|
+
if (opts.route !== void 0) body.route = opts.route;
|
|
1047
|
+
if (opts.includeReasoning !== void 0) body.include_reasoning = opts.includeReasoning;
|
|
1048
|
+
return await this.request("POST", "/memory/search/routed", { body }) ?? {};
|
|
1049
|
+
}
|
|
1050
|
+
/** Compose an answer across several memories, with citations. */
|
|
1051
|
+
async synthesize(opts = {}) {
|
|
1052
|
+
const body = {};
|
|
1053
|
+
if (opts.query !== void 0) body.query = opts.query;
|
|
1054
|
+
if (opts.memoryIds !== void 0) body.memory_ids = opts.memoryIds;
|
|
1055
|
+
if (opts.maxMemories !== void 0) body.max_memories = opts.maxMemories;
|
|
1056
|
+
return await this.request("POST", "/memory/synthesize", { body }) ?? {};
|
|
1057
|
+
}
|
|
1058
|
+
/** Re-embed this end user's memories. Returns immediately (`202`). */
|
|
1059
|
+
async refresh() {
|
|
1060
|
+
return await this.request("POST", "/memory/refresh") ?? {};
|
|
1061
|
+
}
|
|
1062
|
+
// ── Intelligence and graph ─────────────────────────────────────────
|
|
1063
|
+
/** Nodes and typed edges for this end user's memory graph. */
|
|
1064
|
+
async graph(opts = {}) {
|
|
1065
|
+
return await this.request("GET", "/memory/graph", {
|
|
1066
|
+
query: { limit: opts.limit, memory_id: opts.memoryId, depth: opts.depth }
|
|
1067
|
+
}) ?? {};
|
|
1068
|
+
}
|
|
1069
|
+
/** Semantic clusters over this end user's memories. */
|
|
1070
|
+
async clusters(opts = {}) {
|
|
1071
|
+
return await this.request("GET", "/memory/clusters", {
|
|
1072
|
+
query: { limit: opts.limit }
|
|
1073
|
+
}) ?? {};
|
|
1074
|
+
}
|
|
1075
|
+
/** Contradictions and open decisions detected across memories. */
|
|
1076
|
+
async decisions(opts = {}) {
|
|
1077
|
+
return await this.request("GET", "/memory/decisions", {
|
|
1078
|
+
query: { limit: opts.limit }
|
|
1079
|
+
}) ?? {};
|
|
1080
|
+
}
|
|
1081
|
+
/** Record which side of a contradiction wins. */
|
|
1082
|
+
async resolveDecision(opts = {}) {
|
|
1083
|
+
const body = {};
|
|
1084
|
+
if (opts.decisionId !== void 0) body.decision_id = opts.decisionId;
|
|
1085
|
+
if (opts.winningMemoryId !== void 0) body.winning_memory_id = opts.winningMemoryId;
|
|
1086
|
+
if (opts.resolution !== void 0) body.resolution = opts.resolution;
|
|
1087
|
+
if (opts.note !== void 0) body.note = opts.note;
|
|
1088
|
+
return await this.request("POST", "/memory/decision/resolve", { body }) ?? {};
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* The intelligence report: themes, entities, patterns, dual-horizon view.
|
|
1092
|
+
*
|
|
1093
|
+
* `scope` is explicit by design server-side — nothing is inferred, so if you do
|
|
1094
|
+
* not ask for a scope you do not get it.
|
|
1095
|
+
*/
|
|
1096
|
+
async intelligence(opts = {}) {
|
|
1097
|
+
return await this.request("GET", "/memory/intelligence", {
|
|
1098
|
+
query: { limit: opts.limit, scope: opts.scope, project_id: opts.projectId }
|
|
1099
|
+
}) ?? {};
|
|
1100
|
+
}
|
|
1101
|
+
/** Counts and coverage for the knowledge base. */
|
|
1102
|
+
async knowledgeStats() {
|
|
1103
|
+
return await this.request("GET", "/memory/knowledge/stats") ?? {};
|
|
1104
|
+
}
|
|
1105
|
+
// ── v1 data plane ──────────────────────────────────────────────────
|
|
1106
|
+
/** Add a conversation turn and extract memories from it. */
|
|
1107
|
+
async addTurn(req) {
|
|
1108
|
+
const body = {
|
|
1109
|
+
tenant_id: req.tenantId,
|
|
1110
|
+
user_id: req.userId,
|
|
1111
|
+
messages: req.messages
|
|
1112
|
+
};
|
|
1113
|
+
if (req.sessionId !== void 0) body.session_id = req.sessionId;
|
|
1114
|
+
if (req.metadata !== void 0) body.metadata = req.metadata;
|
|
1115
|
+
return await this.request("POST", "/v1/memory/add_turn", { body }) ?? {};
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Build a prompt-ready context block for an LLM call.
|
|
1119
|
+
*
|
|
1120
|
+
* `types` narrows the result to those content types. Names outside the
|
|
1121
|
+
* organization's vocabulary are dropped rather than rejected, so a stale client
|
|
1122
|
+
* gets a narrower answer instead of an error.
|
|
1123
|
+
*/
|
|
1124
|
+
async recall(req) {
|
|
1125
|
+
const body = {
|
|
1126
|
+
tenant_id: req.tenantId,
|
|
1127
|
+
user_id: req.userId,
|
|
1128
|
+
prompt: req.prompt
|
|
1129
|
+
};
|
|
1130
|
+
if (req.k !== void 0) body.k = req.k;
|
|
1131
|
+
if (req.types !== void 0) body.types = req.types;
|
|
1132
|
+
return await this.request("POST", "/v1/memory/recall", { body }) ?? {};
|
|
1133
|
+
}
|
|
1134
|
+
/** Async ingestion status for one memory. */
|
|
1135
|
+
async status(memoryId) {
|
|
1136
|
+
if (!Number.isInteger(memoryId) || memoryId <= 0) {
|
|
1137
|
+
throw new ValidationError("memoryId must be a positive integer");
|
|
1138
|
+
}
|
|
1139
|
+
return await this.request("GET", `/v1/memory/status/${memoryId}`) ?? {};
|
|
1140
|
+
}
|
|
1141
|
+
/** Page through a specific end user's memories. */
|
|
1142
|
+
async listMemories(req) {
|
|
1143
|
+
return await this.request(
|
|
1144
|
+
"GET",
|
|
1145
|
+
`/v1/memory/${encodeURIComponent(req.tenantId)}/${encodeURIComponent(req.userId)}/list`,
|
|
1146
|
+
{ query: { limit: req.limit, offset: req.offset } }
|
|
1147
|
+
) ?? {};
|
|
1148
|
+
}
|
|
736
1149
|
};
|
|
1150
|
+
function toOntology(raw) {
|
|
1151
|
+
return {
|
|
1152
|
+
contentTypes: raw?.content_types ?? [],
|
|
1153
|
+
relationTypes: raw?.relation_types ?? [],
|
|
1154
|
+
builtinContentTypes: raw?.builtin_content_types ?? [],
|
|
1155
|
+
builtinRelationTypes: raw?.builtin_relation_types ?? [],
|
|
1156
|
+
customContentTypes: raw?.custom_content_types ?? [],
|
|
1157
|
+
customRelationTypes: raw?.custom_relation_types ?? [],
|
|
1158
|
+
maxCustomTypes: raw?.max_custom_types ?? 32
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
737
1161
|
export {
|
|
738
1162
|
AuthError,
|
|
739
1163
|
ControlPlaneClient,
|