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.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.0";
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
- if (!Array.isArray(events) || events.length === 0 || events.some((event) => !event.trim())) {
138
- throw new ValidationError("events must contain at least one non-empty event type");
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) positiveId(query.limit, "limit");
294
- if (query.cursor !== void 0) positiveId(query.cursor, "cursor");
295
- if (query.skip !== void 0 && (!Number.isInteger(query.skip) || query.skip < 0)) {
296
- throw new ValidationError("skip must be a non-negative integer");
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", { ...options, body });
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
- validateWebhook("update", request.url, ["validation"]);
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
- if (request.events.length === 0 || request.events.some((event) => !event.trim())) {
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 && (request.statuses.length === 0 || request.statuses.some((status) => !status.trim()))) {
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.1.0";
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(memoryIds, reason) {
672
- if (!Array.isArray(memoryIds) || memoryIds.length === 0) {
673
- throw new ValidationError("memoryIds must be a non-empty array");
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
- const body = { memory_ids: memoryIds };
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,