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.js
CHANGED
|
@@ -74,7 +74,7 @@ var ServerError = class extends MemorySyncError {
|
|
|
74
74
|
};
|
|
75
75
|
|
|
76
76
|
// src/control-plane.ts
|
|
77
|
-
var SDK_VERSION = "1.1.
|
|
77
|
+
var SDK_VERSION = "1.1.1";
|
|
78
78
|
function safeJson(text) {
|
|
79
79
|
try {
|
|
80
80
|
return JSON.parse(text);
|
|
@@ -144,7 +144,22 @@ function positiveId(value, name) {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
function nonEmpty(value, name) {
|
|
147
|
-
if (!value.trim()) throw new ValidationError(`${name} must not be empty`);
|
|
147
|
+
if (typeof value !== "string" || !value.trim()) throw new ValidationError(`${name} must not be empty`);
|
|
148
|
+
}
|
|
149
|
+
function boundedInteger(value, name, minimum, maximum) {
|
|
150
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
151
|
+
throw new ValidationError(`${name} must be an integer between ${minimum} and ${maximum}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function nonNegativeInteger(value, name) {
|
|
155
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
156
|
+
throw new ValidationError(`${name} must be a non-negative integer`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function nonEmptyStrings(values, name) {
|
|
160
|
+
if (!Array.isArray(values) || values.length === 0 || values.some((value) => typeof value !== "string" || !value.trim())) {
|
|
161
|
+
throw new ValidationError(`${name} must contain at least one non-empty string`);
|
|
162
|
+
}
|
|
148
163
|
}
|
|
149
164
|
function webhookRetryConfig(config) {
|
|
150
165
|
const wire = {};
|
|
@@ -167,9 +182,11 @@ function webhookSignatureConfig(config) {
|
|
|
167
182
|
function validateWebhook(name, url, events) {
|
|
168
183
|
nonEmpty(name, "name");
|
|
169
184
|
if (name.length > 128) throw new ValidationError("name may contain at most 128 characters");
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
185
|
+
nonEmptyStrings(events, "events");
|
|
186
|
+
validateWebhookUrl(url);
|
|
187
|
+
}
|
|
188
|
+
function validateWebhookUrl(url) {
|
|
189
|
+
nonEmpty(url, "url");
|
|
173
190
|
let parsed;
|
|
174
191
|
try {
|
|
175
192
|
parsed = new URL(url);
|
|
@@ -323,10 +340,11 @@ var ControlPlaneClient = class {
|
|
|
323
340
|
return this.request("POST", `/auth/sessions/${sessionId}/revoke`, options);
|
|
324
341
|
}
|
|
325
342
|
async listAuditEvents(query = {}, options = {}) {
|
|
326
|
-
if (query.limit !== void 0)
|
|
327
|
-
if (query.cursor !== void 0)
|
|
328
|
-
if (query.skip !== void 0
|
|
329
|
-
|
|
343
|
+
if (query.limit !== void 0) boundedInteger(query.limit, "limit", 1, 200);
|
|
344
|
+
if (query.cursor !== void 0) nonNegativeInteger(query.cursor, "cursor");
|
|
345
|
+
if (query.skip !== void 0) nonNegativeInteger(query.skip, "skip");
|
|
346
|
+
if (query.sortDirection !== void 0 && query.sortDirection !== "asc" && query.sortDirection !== "desc") {
|
|
347
|
+
throw new ValidationError("sortDirection must be 'asc' or 'desc'");
|
|
330
348
|
}
|
|
331
349
|
const path = "/admin/audit-logs" + queryString({
|
|
332
350
|
limit: query.limit,
|
|
@@ -347,7 +365,8 @@ var ControlPlaneClient = class {
|
|
|
347
365
|
success: query.success,
|
|
348
366
|
source: query.source,
|
|
349
367
|
ingest_method: query.ingestMethod,
|
|
350
|
-
search: query.search
|
|
368
|
+
search: query.search,
|
|
369
|
+
include_stats: query.includeStats
|
|
351
370
|
});
|
|
352
371
|
const raw = await this.request("GET", path, options);
|
|
353
372
|
return {
|
|
@@ -358,6 +377,7 @@ var ControlPlaneClient = class {
|
|
|
358
377
|
};
|
|
359
378
|
}
|
|
360
379
|
async listIntegrations(query = {}, options = {}) {
|
|
380
|
+
if (query.category !== void 0) nonEmpty(query.category, "category");
|
|
361
381
|
const path = "/api/v1/integrations/catalog" + queryString({ category: query.category });
|
|
362
382
|
return this.request("GET", path, options);
|
|
363
383
|
}
|
|
@@ -388,12 +408,20 @@ var ControlPlaneClient = class {
|
|
|
388
408
|
throw new ValidationError("description may contain at most 500 characters");
|
|
389
409
|
}
|
|
390
410
|
if (request.projectId !== void 0) nonEmpty(request.projectId, "projectId");
|
|
411
|
+
if (options.projectId !== void 0) nonEmpty(options.projectId, "projectId override");
|
|
412
|
+
if (request.projectId && options.projectId && request.projectId.trim() !== options.projectId.trim()) {
|
|
413
|
+
throw new ValidationError("request projectId and options projectId must match");
|
|
414
|
+
}
|
|
391
415
|
const body = { name: request.name, url: request.url, events: request.events };
|
|
392
416
|
if (request.description !== void 0) body.description = request.description;
|
|
393
417
|
if (request.retryConfig !== void 0) body.retry_config = webhookRetryConfig(request.retryConfig);
|
|
394
418
|
if (request.signatureConfig !== void 0) body.signature_config = webhookSignatureConfig(request.signatureConfig);
|
|
395
419
|
if (request.projectId !== void 0) body.project_id = request.projectId;
|
|
396
|
-
return this.request("POST", "/org/webhooks", {
|
|
420
|
+
return this.request("POST", "/org/webhooks", {
|
|
421
|
+
...options,
|
|
422
|
+
projectId: options.projectId ?? request.projectId,
|
|
423
|
+
body
|
|
424
|
+
});
|
|
397
425
|
}
|
|
398
426
|
async listWebhooks(options = {}) {
|
|
399
427
|
return this.request("GET", "/org/webhooks", options);
|
|
@@ -407,7 +435,7 @@ var ControlPlaneClient = class {
|
|
|
407
435
|
body.name = request.name;
|
|
408
436
|
}
|
|
409
437
|
if (request.url !== void 0) {
|
|
410
|
-
|
|
438
|
+
validateWebhookUrl(request.url);
|
|
411
439
|
body.url = request.url;
|
|
412
440
|
}
|
|
413
441
|
if (request.description !== void 0) {
|
|
@@ -417,9 +445,7 @@ var ControlPlaneClient = class {
|
|
|
417
445
|
body.description = request.description;
|
|
418
446
|
}
|
|
419
447
|
if (request.events !== void 0) {
|
|
420
|
-
|
|
421
|
-
throw new ValidationError("events must contain at least one non-empty event type");
|
|
422
|
-
}
|
|
448
|
+
nonEmptyStrings(request.events, "events");
|
|
423
449
|
body.events = request.events;
|
|
424
450
|
}
|
|
425
451
|
if (request.retryConfig !== void 0) body.retry_config = webhookRetryConfig(request.retryConfig);
|
|
@@ -450,9 +476,7 @@ var ControlPlaneClient = class {
|
|
|
450
476
|
if (request.limit !== void 0 && (!Number.isInteger(request.limit) || request.limit < 1 || request.limit > 1e3)) {
|
|
451
477
|
throw new ValidationError("limit must be an integer between 1 and 1000");
|
|
452
478
|
}
|
|
453
|
-
if (request.statuses !== void 0
|
|
454
|
-
throw new ValidationError("statuses must contain at least one non-empty status");
|
|
455
|
-
}
|
|
479
|
+
if (request.statuses !== void 0) nonEmptyStrings(request.statuses, "statuses");
|
|
456
480
|
const body = {};
|
|
457
481
|
if (request.sinceMinutes !== void 0) body.since_minutes = request.sinceMinutes;
|
|
458
482
|
if (request.statuses !== void 0) body.statuses = request.statuses;
|
|
@@ -476,7 +500,7 @@ var ControlPlaneClient = class {
|
|
|
476
500
|
};
|
|
477
501
|
|
|
478
502
|
// src/index.ts
|
|
479
|
-
var SDK_VERSION2 = "1.
|
|
503
|
+
var SDK_VERSION2 = "1.2.0";
|
|
480
504
|
function camelToSnakeKey(key) {
|
|
481
505
|
return key.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
482
506
|
}
|
|
@@ -505,6 +529,22 @@ function snakeToCamelMemory(m) {
|
|
|
505
529
|
score: m.score ?? null
|
|
506
530
|
};
|
|
507
531
|
}
|
|
532
|
+
function buildQuery(params) {
|
|
533
|
+
if (!params) return "";
|
|
534
|
+
const search = new URLSearchParams();
|
|
535
|
+
for (const [key, value] of Object.entries(params)) {
|
|
536
|
+
if (value === void 0 || value === null) continue;
|
|
537
|
+
if (Array.isArray(value)) {
|
|
538
|
+
for (const item of value) {
|
|
539
|
+
if (item !== void 0 && item !== null) search.append(key, String(item));
|
|
540
|
+
}
|
|
541
|
+
} else {
|
|
542
|
+
search.append(key, String(value));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const qs = search.toString();
|
|
546
|
+
return qs ? `?${qs}` : "";
|
|
547
|
+
}
|
|
508
548
|
function safeJson2(text) {
|
|
509
549
|
try {
|
|
510
550
|
return JSON.parse(text);
|
|
@@ -564,17 +604,20 @@ var MemorySyncClient = class {
|
|
|
564
604
|
return h;
|
|
565
605
|
}
|
|
566
606
|
async request(method, path, options = {}) {
|
|
567
|
-
const url = `${this.baseUrl}${path}`;
|
|
607
|
+
const url = `${this.baseUrl}${path}${buildQuery(options.query)}`;
|
|
568
608
|
const controller = new AbortController();
|
|
569
609
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
570
610
|
try {
|
|
571
611
|
const headers = this.headers(
|
|
572
612
|
options.endUserOverride ? { "X-End-User-ID": options.endUserOverride } : {}
|
|
573
613
|
);
|
|
614
|
+
if (options.form) {
|
|
615
|
+
delete headers["Content-Type"];
|
|
616
|
+
}
|
|
574
617
|
const res = await this.fetchImpl(url, {
|
|
575
618
|
method,
|
|
576
619
|
headers,
|
|
577
|
-
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
|
|
620
|
+
body: options.form ? options.form : options.body !== void 0 ? JSON.stringify(options.body) : void 0,
|
|
578
621
|
signal: controller.signal
|
|
579
622
|
});
|
|
580
623
|
const requestId = res.headers.get("X-Request-ID") ?? void 0;
|
|
@@ -701,14 +744,51 @@ var MemorySyncClient = class {
|
|
|
701
744
|
const raw = await this.request("PATCH", `/memory/${memoryId}`, { body });
|
|
702
745
|
return snakeToCamelMemory(raw);
|
|
703
746
|
}
|
|
704
|
-
async forget(
|
|
705
|
-
|
|
706
|
-
|
|
747
|
+
async forget(arg, legacyReason) {
|
|
748
|
+
const req = Array.isArray(arg) ? { memoryIds: arg, reason: legacyReason } : arg;
|
|
749
|
+
const hasIds = req.memoryIds !== void 0;
|
|
750
|
+
const hasFilters = req.filters !== void 0;
|
|
751
|
+
if (hasIds && hasFilters) {
|
|
752
|
+
throw new ValidationError("Provide either memoryIds or filters, not both");
|
|
753
|
+
}
|
|
754
|
+
if (!hasIds && !hasFilters) {
|
|
755
|
+
throw new ValidationError("Provide either memoryIds or filters");
|
|
756
|
+
}
|
|
757
|
+
const body = {};
|
|
758
|
+
if (hasIds) {
|
|
759
|
+
if (!Array.isArray(req.memoryIds) || req.memoryIds.length === 0) {
|
|
760
|
+
throw new ValidationError("memoryIds must be a non-empty array");
|
|
761
|
+
}
|
|
762
|
+
body.memory_ids = req.memoryIds;
|
|
763
|
+
} else {
|
|
764
|
+
const f = req.filters;
|
|
765
|
+
const filters = {};
|
|
766
|
+
if (f.source !== void 0) filters.source = f.source;
|
|
767
|
+
if (f.eventType !== void 0) filters.event_type = f.eventType;
|
|
768
|
+
if (f.tags !== void 0) filters.tags = f.tags;
|
|
769
|
+
if (f.tier !== void 0) filters.tier = f.tier;
|
|
770
|
+
if (f.before !== void 0) filters.before = f.before;
|
|
771
|
+
if (f.after !== void 0) filters.after = f.after;
|
|
772
|
+
if (Object.keys(filters).length === 0) {
|
|
773
|
+
throw new ValidationError(
|
|
774
|
+
"filters must set at least one criterion; use purgeUser() to remove everything for an end user"
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
body.filters = filters;
|
|
778
|
+
if (req.dryRun) body.dry_run = true;
|
|
707
779
|
}
|
|
708
|
-
|
|
709
|
-
if (reason !== void 0) body.reason = reason;
|
|
780
|
+
if (req.reason !== void 0) body.reason = req.reason;
|
|
710
781
|
return await this.request("DELETE", "/memory/forget", { body });
|
|
711
782
|
}
|
|
783
|
+
/**
|
|
784
|
+
* Delete every memory belonging to the calling end user.
|
|
785
|
+
*
|
|
786
|
+
* Separate from {@link forget} on purpose: this reads like what it does, so a
|
|
787
|
+
* whole-namespace delete can never be the accidental result of an empty filter.
|
|
788
|
+
*/
|
|
789
|
+
async purgeUser() {
|
|
790
|
+
return await this.request("DELETE", "/memory/user/purge") ?? {};
|
|
791
|
+
}
|
|
712
792
|
async summarize(req) {
|
|
713
793
|
if (!req.memoryIds || req.memoryIds.length === 0) {
|
|
714
794
|
throw new ValidationError("summarize() requires memoryIds");
|
|
@@ -766,7 +846,351 @@ var MemorySyncClient = class {
|
|
|
766
846
|
createdAt: raw.created_at
|
|
767
847
|
};
|
|
768
848
|
}
|
|
849
|
+
// ── Files ──────────────────────────────────────────────────────────
|
|
850
|
+
/**
|
|
851
|
+
* Ingest a document and store the memories extracted from its text.
|
|
852
|
+
*
|
|
853
|
+
* Accepts the formats the connectors accept — PDF, DOCX, PPTX, XLSX, CSV,
|
|
854
|
+
* text, Markdown, HTML, source code, and images/audio/video where
|
|
855
|
+
* transcription is configured.
|
|
856
|
+
*
|
|
857
|
+
* Billed as an add, one unit per memory created. Resolves to the first stored
|
|
858
|
+
* memory, or an {@link AddSkippedResponse} when the file yielded nothing worth
|
|
859
|
+
* keeping — a blank scan, a sheet of empty cells, or content the extractor
|
|
860
|
+
* judges trivial are all normal outcomes rather than errors.
|
|
861
|
+
*/
|
|
862
|
+
async upload(req) {
|
|
863
|
+
if (!req.filename?.trim()) {
|
|
864
|
+
throw new ValidationError("filename is required so the server can pick a parser");
|
|
865
|
+
}
|
|
866
|
+
const form = new FormData();
|
|
867
|
+
const blob = req.file instanceof Uint8Array ? new Blob([req.file], {
|
|
868
|
+
type: req.contentType ?? "application/octet-stream"
|
|
869
|
+
}) : req.file;
|
|
870
|
+
form.append("file", blob, req.filename);
|
|
871
|
+
if (req.source !== void 0) form.append("source", req.source);
|
|
872
|
+
if (req.metadata !== void 0) form.append("metadata", JSON.stringify(req.metadata));
|
|
873
|
+
if (req.endUserId !== void 0) form.append("end_user_id", req.endUserId);
|
|
874
|
+
const raw = await this.request("POST", "/memory/upload", {
|
|
875
|
+
form,
|
|
876
|
+
endUserOverride: req.endUserId
|
|
877
|
+
});
|
|
878
|
+
if (raw && raw.status === "skipped") {
|
|
879
|
+
return {
|
|
880
|
+
status: "skipped",
|
|
881
|
+
reason: raw.reason ?? "no_extractable_text",
|
|
882
|
+
memoryIds: raw.memory_ids ?? [],
|
|
883
|
+
candidatesExtracted: raw.candidates_extracted ?? 0,
|
|
884
|
+
candidatesStored: raw.candidates_stored ?? 0
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
return snakeToCamelMemory(raw);
|
|
888
|
+
}
|
|
889
|
+
// ── Bulk edit ──────────────────────────────────────────────────────
|
|
890
|
+
/**
|
|
891
|
+
* Apply many metadata edits in one request.
|
|
892
|
+
*
|
|
893
|
+
* Editable: `tags`, `importance`, `metadata`, `source`, `eventType`. A memory's
|
|
894
|
+
* text, embeddings, owner, environment and project are not editable.
|
|
895
|
+
*
|
|
896
|
+
* Applied in one transaction, so the batch either lands or it does not — but an
|
|
897
|
+
* id the caller cannot see is reported per item rather than failing the request.
|
|
898
|
+
*/
|
|
899
|
+
async batchUpdate(items) {
|
|
900
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
901
|
+
throw new ValidationError("items must contain at least one entry");
|
|
902
|
+
}
|
|
903
|
+
if (items.length > 100) {
|
|
904
|
+
throw new ValidationError("items may contain at most 100 entries per request");
|
|
905
|
+
}
|
|
906
|
+
const seen = /* @__PURE__ */ new Map();
|
|
907
|
+
const payload = items.map((item, index) => {
|
|
908
|
+
if (!Number.isInteger(item.memoryId) || item.memoryId <= 0) {
|
|
909
|
+
throw new ValidationError(`items[${index}].memoryId must be a positive integer`);
|
|
910
|
+
}
|
|
911
|
+
const o = { memory_id: item.memoryId };
|
|
912
|
+
if (item.tags !== void 0) o.tags = item.tags;
|
|
913
|
+
if (item.importance !== void 0) o.importance = item.importance;
|
|
914
|
+
if (item.metadata !== void 0) o.metadata = item.metadata;
|
|
915
|
+
if (item.source !== void 0) o.source = item.source;
|
|
916
|
+
if (item.eventType !== void 0) o.event_type = item.eventType;
|
|
917
|
+
if (Object.keys(o).length === 1) {
|
|
918
|
+
throw new ValidationError(
|
|
919
|
+
`items[${index}] (memoryId ${item.memoryId}): at least one updatable field must be provided`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
const previous = seen.get(item.memoryId);
|
|
923
|
+
if (previous !== void 0) {
|
|
924
|
+
throw new ValidationError(
|
|
925
|
+
`items must not contain the same memoryId twice: ${item.memoryId} appears at index ${previous} and ${index}`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
seen.set(item.memoryId, index);
|
|
929
|
+
return o;
|
|
930
|
+
});
|
|
931
|
+
const raw = await this.request("POST", "/memory/batch-update", {
|
|
932
|
+
body: { items: payload }
|
|
933
|
+
});
|
|
934
|
+
return {
|
|
935
|
+
total: raw?.total ?? 0,
|
|
936
|
+
updated: raw?.updated ?? 0,
|
|
937
|
+
notFound: raw?.not_found ?? 0,
|
|
938
|
+
results: (raw?.results ?? []).map((r) => ({
|
|
939
|
+
index: r.index,
|
|
940
|
+
memoryId: r.memory_id,
|
|
941
|
+
status: r.status,
|
|
942
|
+
changedFields: r.changed_fields ?? []
|
|
943
|
+
}))
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
// ── History and feedback ───────────────────────────────────────────
|
|
947
|
+
/**
|
|
948
|
+
* Recorded changes to one memory, oldest first.
|
|
949
|
+
*
|
|
950
|
+
* Entry 0 is the creation. Later entries carry the old and new value per field.
|
|
951
|
+
* Entries written by background workers have `actor: null`. Only
|
|
952
|
+
* user-meaningful fields are tracked; the watched list comes back in
|
|
953
|
+
* `trackedFields`.
|
|
954
|
+
*/
|
|
955
|
+
async history(memoryId, opts = {}) {
|
|
956
|
+
if (!Number.isInteger(memoryId) || memoryId <= 0) {
|
|
957
|
+
throw new ValidationError("memoryId must be a positive integer");
|
|
958
|
+
}
|
|
959
|
+
const raw = await this.request(
|
|
960
|
+
"GET",
|
|
961
|
+
`/memory/${memoryId}/history`,
|
|
962
|
+
{ query: { limit: opts.limit ?? 100, offset: opts.offset ?? 0 } }
|
|
963
|
+
);
|
|
964
|
+
return {
|
|
965
|
+
memoryId: raw?.memory_id ?? memoryId,
|
|
966
|
+
total: raw?.total ?? 0,
|
|
967
|
+
revisions: (raw?.revisions ?? []).map((r) => ({
|
|
968
|
+
revision: r.revision,
|
|
969
|
+
event: r.event,
|
|
970
|
+
changedFields: r.changed_fields ?? [],
|
|
971
|
+
diff: r.diff ?? {},
|
|
972
|
+
actor: r.actor ?? null,
|
|
973
|
+
createdAt: r.created_at
|
|
974
|
+
})),
|
|
975
|
+
trackedFields: raw?.tracked_fields ?? []
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Tell MemorySync whether a memory was useful.
|
|
980
|
+
*
|
|
981
|
+
* By default this moves the memory's `importance`, a weighted retrieval-ranking
|
|
982
|
+
* factor, so a memory marked useful surfaces more readily and one marked wrong
|
|
983
|
+
* surfaces less. The size of the move is adaptive: consistent signals amplify
|
|
984
|
+
* it, mixed signals damp it. Importance is clamped to [0.05, 1.0], so no run of
|
|
985
|
+
* negative feedback can make a memory permanently unreachable. Not billed.
|
|
986
|
+
*/
|
|
987
|
+
async feedback(memoryId, signal, opts = {}) {
|
|
988
|
+
if (!Number.isInteger(memoryId) || memoryId <= 0) {
|
|
989
|
+
throw new ValidationError("memoryId must be a positive integer");
|
|
990
|
+
}
|
|
991
|
+
const valid = ["positive", "negative", "retrieved", "ignored"];
|
|
992
|
+
if (!valid.includes(signal)) {
|
|
993
|
+
throw new ValidationError(`signal must be one of ${valid.join(", ")}; got ${String(signal)}`);
|
|
994
|
+
}
|
|
995
|
+
const body = { signal };
|
|
996
|
+
if (opts.comment !== void 0) body.comment = opts.comment;
|
|
997
|
+
const raw = await this.request(
|
|
998
|
+
"POST",
|
|
999
|
+
`/memory/${memoryId}/feedback`,
|
|
1000
|
+
{ body }
|
|
1001
|
+
);
|
|
1002
|
+
const summary = raw?.summary ?? {};
|
|
1003
|
+
const trend = summary.trend ?? {};
|
|
1004
|
+
return {
|
|
1005
|
+
memoryId: raw?.memory_id ?? memoryId,
|
|
1006
|
+
signal: raw?.signal ?? signal,
|
|
1007
|
+
importanceBefore: raw?.importance_before ?? 0,
|
|
1008
|
+
importanceAfter: raw?.importance_after ?? 0,
|
|
1009
|
+
adjustment: raw?.adjustment ?? 0,
|
|
1010
|
+
influencedRanking: Boolean(raw?.influenced_ranking),
|
|
1011
|
+
summary: {
|
|
1012
|
+
totalSignals: summary.total_signals ?? 0,
|
|
1013
|
+
signalCounts: summary.signal_counts ?? {},
|
|
1014
|
+
trend: {
|
|
1015
|
+
momentum: trend.momentum ?? "neutral",
|
|
1016
|
+
consistency: trend.consistency ?? 0,
|
|
1017
|
+
trendMultiplier: trend.trend_multiplier ?? 1,
|
|
1018
|
+
recentCount: trend.recent_count ?? 0
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
// ── Ontology ───────────────────────────────────────────────────────
|
|
1024
|
+
/** The memory vocabulary in effect for this organization. */
|
|
1025
|
+
async getOntology() {
|
|
1026
|
+
const raw = await this.request("GET", "/memory/ontology");
|
|
1027
|
+
return toOntology(raw);
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Replace this organization's *additions* to the vocabulary.
|
|
1031
|
+
*
|
|
1032
|
+
* The two vocabularies are independent: omit one and it is left untouched, so
|
|
1033
|
+
* adding a content type cannot wipe your relation types. Pass an empty array to
|
|
1034
|
+
* clear a vocabulary's custom entries. The built-in types always remain.
|
|
1035
|
+
*/
|
|
1036
|
+
async updateOntology(req) {
|
|
1037
|
+
if (req.contentTypes === void 0 && req.relationTypes === void 0) {
|
|
1038
|
+
throw new ValidationError(
|
|
1039
|
+
"provide contentTypes, relationTypes, or both; an empty request would silently do nothing"
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
const body = {};
|
|
1043
|
+
if (req.contentTypes !== void 0) body.content_types = req.contentTypes;
|
|
1044
|
+
if (req.relationTypes !== void 0) body.relation_types = req.relationTypes;
|
|
1045
|
+
const raw = await this.request("PUT", "/memory/ontology", { body });
|
|
1046
|
+
return toOntology(raw);
|
|
1047
|
+
}
|
|
1048
|
+
// ── Retrieval variants ─────────────────────────────────────────────
|
|
1049
|
+
/**
|
|
1050
|
+
* Alias of {@link query} against `/memory/retrieve`.
|
|
1051
|
+
*
|
|
1052
|
+
* Both paths are live, and integrators arriving from other platforms reach for
|
|
1053
|
+
* `retrieve`. Identical semantics.
|
|
1054
|
+
*/
|
|
1055
|
+
async retrieve(req) {
|
|
1056
|
+
const body = { query: req.query };
|
|
1057
|
+
if (req.k !== void 0) body.k = req.k;
|
|
1058
|
+
if (req.filters !== void 0) body.filters = camelToSnakeShallow(req.filters);
|
|
1059
|
+
if (req.sessionId !== void 0) body.session_id = req.sessionId;
|
|
1060
|
+
if (req.traversalDepth !== void 0) body.traversal_depth = req.traversalDepth;
|
|
1061
|
+
const raw = await this.request("POST", "/memory/retrieve", { body });
|
|
1062
|
+
return {
|
|
1063
|
+
memories: (raw.memories ?? []).map(snakeToCamelMemory),
|
|
1064
|
+
context: raw.context ?? null,
|
|
1065
|
+
latencyMs: raw.latency_ms ?? null,
|
|
1066
|
+
sessionId: raw.session_id ?? null,
|
|
1067
|
+
queryIntent: raw.query_intent ?? null
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Route a question to the best knowledge source and answer from it.
|
|
1072
|
+
*
|
|
1073
|
+
* Returns the raw payload: the response carries routing diagnostics whose shape
|
|
1074
|
+
* is richer and more volatile than an SDK should freeze into an interface.
|
|
1075
|
+
*/
|
|
1076
|
+
async searchRouted(query, opts = {}) {
|
|
1077
|
+
const body = { query };
|
|
1078
|
+
if (opts.k !== void 0) body.k = opts.k;
|
|
1079
|
+
if (opts.route !== void 0) body.route = opts.route;
|
|
1080
|
+
if (opts.includeReasoning !== void 0) body.include_reasoning = opts.includeReasoning;
|
|
1081
|
+
return await this.request("POST", "/memory/search/routed", { body }) ?? {};
|
|
1082
|
+
}
|
|
1083
|
+
/** Compose an answer across several memories, with citations. */
|
|
1084
|
+
async synthesize(opts = {}) {
|
|
1085
|
+
const body = {};
|
|
1086
|
+
if (opts.query !== void 0) body.query = opts.query;
|
|
1087
|
+
if (opts.memoryIds !== void 0) body.memory_ids = opts.memoryIds;
|
|
1088
|
+
if (opts.maxMemories !== void 0) body.max_memories = opts.maxMemories;
|
|
1089
|
+
return await this.request("POST", "/memory/synthesize", { body }) ?? {};
|
|
1090
|
+
}
|
|
1091
|
+
/** Re-embed this end user's memories. Returns immediately (`202`). */
|
|
1092
|
+
async refresh() {
|
|
1093
|
+
return await this.request("POST", "/memory/refresh") ?? {};
|
|
1094
|
+
}
|
|
1095
|
+
// ── Intelligence and graph ─────────────────────────────────────────
|
|
1096
|
+
/** Nodes and typed edges for this end user's memory graph. */
|
|
1097
|
+
async graph(opts = {}) {
|
|
1098
|
+
return await this.request("GET", "/memory/graph", {
|
|
1099
|
+
query: { limit: opts.limit, memory_id: opts.memoryId, depth: opts.depth }
|
|
1100
|
+
}) ?? {};
|
|
1101
|
+
}
|
|
1102
|
+
/** Semantic clusters over this end user's memories. */
|
|
1103
|
+
async clusters(opts = {}) {
|
|
1104
|
+
return await this.request("GET", "/memory/clusters", {
|
|
1105
|
+
query: { limit: opts.limit }
|
|
1106
|
+
}) ?? {};
|
|
1107
|
+
}
|
|
1108
|
+
/** Contradictions and open decisions detected across memories. */
|
|
1109
|
+
async decisions(opts = {}) {
|
|
1110
|
+
return await this.request("GET", "/memory/decisions", {
|
|
1111
|
+
query: { limit: opts.limit }
|
|
1112
|
+
}) ?? {};
|
|
1113
|
+
}
|
|
1114
|
+
/** Record which side of a contradiction wins. */
|
|
1115
|
+
async resolveDecision(opts = {}) {
|
|
1116
|
+
const body = {};
|
|
1117
|
+
if (opts.decisionId !== void 0) body.decision_id = opts.decisionId;
|
|
1118
|
+
if (opts.winningMemoryId !== void 0) body.winning_memory_id = opts.winningMemoryId;
|
|
1119
|
+
if (opts.resolution !== void 0) body.resolution = opts.resolution;
|
|
1120
|
+
if (opts.note !== void 0) body.note = opts.note;
|
|
1121
|
+
return await this.request("POST", "/memory/decision/resolve", { body }) ?? {};
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* The intelligence report: themes, entities, patterns, dual-horizon view.
|
|
1125
|
+
*
|
|
1126
|
+
* `scope` is explicit by design server-side — nothing is inferred, so if you do
|
|
1127
|
+
* not ask for a scope you do not get it.
|
|
1128
|
+
*/
|
|
1129
|
+
async intelligence(opts = {}) {
|
|
1130
|
+
return await this.request("GET", "/memory/intelligence", {
|
|
1131
|
+
query: { limit: opts.limit, scope: opts.scope, project_id: opts.projectId }
|
|
1132
|
+
}) ?? {};
|
|
1133
|
+
}
|
|
1134
|
+
/** Counts and coverage for the knowledge base. */
|
|
1135
|
+
async knowledgeStats() {
|
|
1136
|
+
return await this.request("GET", "/memory/knowledge/stats") ?? {};
|
|
1137
|
+
}
|
|
1138
|
+
// ── v1 data plane ──────────────────────────────────────────────────
|
|
1139
|
+
/** Add a conversation turn and extract memories from it. */
|
|
1140
|
+
async addTurn(req) {
|
|
1141
|
+
const body = {
|
|
1142
|
+
tenant_id: req.tenantId,
|
|
1143
|
+
user_id: req.userId,
|
|
1144
|
+
messages: req.messages
|
|
1145
|
+
};
|
|
1146
|
+
if (req.sessionId !== void 0) body.session_id = req.sessionId;
|
|
1147
|
+
if (req.metadata !== void 0) body.metadata = req.metadata;
|
|
1148
|
+
return await this.request("POST", "/v1/memory/add_turn", { body }) ?? {};
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Build a prompt-ready context block for an LLM call.
|
|
1152
|
+
*
|
|
1153
|
+
* `types` narrows the result to those content types. Names outside the
|
|
1154
|
+
* organization's vocabulary are dropped rather than rejected, so a stale client
|
|
1155
|
+
* gets a narrower answer instead of an error.
|
|
1156
|
+
*/
|
|
1157
|
+
async recall(req) {
|
|
1158
|
+
const body = {
|
|
1159
|
+
tenant_id: req.tenantId,
|
|
1160
|
+
user_id: req.userId,
|
|
1161
|
+
prompt: req.prompt
|
|
1162
|
+
};
|
|
1163
|
+
if (req.k !== void 0) body.k = req.k;
|
|
1164
|
+
if (req.types !== void 0) body.types = req.types;
|
|
1165
|
+
return await this.request("POST", "/v1/memory/recall", { body }) ?? {};
|
|
1166
|
+
}
|
|
1167
|
+
/** Async ingestion status for one memory. */
|
|
1168
|
+
async status(memoryId) {
|
|
1169
|
+
if (!Number.isInteger(memoryId) || memoryId <= 0) {
|
|
1170
|
+
throw new ValidationError("memoryId must be a positive integer");
|
|
1171
|
+
}
|
|
1172
|
+
return await this.request("GET", `/v1/memory/status/${memoryId}`) ?? {};
|
|
1173
|
+
}
|
|
1174
|
+
/** Page through a specific end user's memories. */
|
|
1175
|
+
async listMemories(req) {
|
|
1176
|
+
return await this.request(
|
|
1177
|
+
"GET",
|
|
1178
|
+
`/v1/memory/${encodeURIComponent(req.tenantId)}/${encodeURIComponent(req.userId)}/list`,
|
|
1179
|
+
{ query: { limit: req.limit, offset: req.offset } }
|
|
1180
|
+
) ?? {};
|
|
1181
|
+
}
|
|
769
1182
|
};
|
|
1183
|
+
function toOntology(raw) {
|
|
1184
|
+
return {
|
|
1185
|
+
contentTypes: raw?.content_types ?? [],
|
|
1186
|
+
relationTypes: raw?.relation_types ?? [],
|
|
1187
|
+
builtinContentTypes: raw?.builtin_content_types ?? [],
|
|
1188
|
+
builtinRelationTypes: raw?.builtin_relation_types ?? [],
|
|
1189
|
+
customContentTypes: raw?.custom_content_types ?? [],
|
|
1190
|
+
customRelationTypes: raw?.custom_relation_types ?? [],
|
|
1191
|
+
maxCustomTypes: raw?.max_custom_types ?? 32
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
770
1194
|
// Annotate the CommonJS export names for ESM import in node:
|
|
771
1195
|
0 && (module.exports = {
|
|
772
1196
|
AuthError,
|