memorysync-sdk 1.1.1 → 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.js CHANGED
@@ -500,7 +500,7 @@ var ControlPlaneClient = class {
500
500
  };
501
501
 
502
502
  // src/index.ts
503
- var SDK_VERSION2 = "1.1.0";
503
+ var SDK_VERSION2 = "1.2.0";
504
504
  function camelToSnakeKey(key) {
505
505
  return key.replace(/([A-Z])/g, "_$1").toLowerCase();
506
506
  }
@@ -529,6 +529,22 @@ function snakeToCamelMemory(m) {
529
529
  score: m.score ?? null
530
530
  };
531
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
+ }
532
548
  function safeJson2(text) {
533
549
  try {
534
550
  return JSON.parse(text);
@@ -588,17 +604,20 @@ var MemorySyncClient = class {
588
604
  return h;
589
605
  }
590
606
  async request(method, path, options = {}) {
591
- const url = `${this.baseUrl}${path}`;
607
+ const url = `${this.baseUrl}${path}${buildQuery(options.query)}`;
592
608
  const controller = new AbortController();
593
609
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
594
610
  try {
595
611
  const headers = this.headers(
596
612
  options.endUserOverride ? { "X-End-User-ID": options.endUserOverride } : {}
597
613
  );
614
+ if (options.form) {
615
+ delete headers["Content-Type"];
616
+ }
598
617
  const res = await this.fetchImpl(url, {
599
618
  method,
600
619
  headers,
601
- 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,
602
621
  signal: controller.signal
603
622
  });
604
623
  const requestId = res.headers.get("X-Request-ID") ?? void 0;
@@ -725,14 +744,51 @@ var MemorySyncClient = class {
725
744
  const raw = await this.request("PATCH", `/memory/${memoryId}`, { body });
726
745
  return snakeToCamelMemory(raw);
727
746
  }
728
- async forget(memoryIds, reason) {
729
- if (!Array.isArray(memoryIds) || memoryIds.length === 0) {
730
- throw new ValidationError("memoryIds must be a non-empty array");
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");
731
756
  }
732
- const body = { memory_ids: memoryIds };
733
- if (reason !== void 0) body.reason = reason;
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;
779
+ }
780
+ if (req.reason !== void 0) body.reason = req.reason;
734
781
  return await this.request("DELETE", "/memory/forget", { body });
735
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
+ }
736
792
  async summarize(req) {
737
793
  if (!req.memoryIds || req.memoryIds.length === 0) {
738
794
  throw new ValidationError("summarize() requires memoryIds");
@@ -790,7 +846,351 @@ var MemorySyncClient = class {
790
846
  createdAt: raw.created_at
791
847
  };
792
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
+ }
793
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
+ }
794
1194
  // Annotate the CommonJS export names for ESM import in node:
795
1195
  0 && (module.exports = {
796
1196
  AuthError,