@maximem/synap-js-sdk 0.4.2 → 0.4.3

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.cts CHANGED
@@ -299,6 +299,18 @@ declare class HttpTransport {
299
299
  currentCredentials(): Credentials;
300
300
  /** Write back identity resolved from `GET /api/v1/auth/whoami`. */
301
301
  updateCredentials(patch: Partial<Credentials>): void;
302
+ /**
303
+ * The instance's scoping mode, from whoami. Lives here rather than on the
304
+ * client because every interface factory already receives the transport,
305
+ * and it is per-client, so a module-level global would be wrong the moment a
306
+ * process holds two clients.
307
+ *
308
+ * undefined means the server did not report it, which is true of any
309
+ * deployment older than the field, and must mean "enforce nothing".
310
+ */
311
+ userContextIsolation: string | undefined;
312
+ /** Refuse a customer_id on a B2C instance, at the call site. */
313
+ checkCustomerId(customerId: unknown, where: string): void;
302
314
  setTimeouts(timeouts: Partial<TimeoutConfig>): void;
303
315
  /** `null` restores the default policy, matching Python's `retry_policy=None`. */
304
316
  setRetryPolicy(policy: Partial<RetryPolicy> | null): void;
package/dist/index.d.ts CHANGED
@@ -299,6 +299,18 @@ declare class HttpTransport {
299
299
  currentCredentials(): Credentials;
300
300
  /** Write back identity resolved from `GET /api/v1/auth/whoami`. */
301
301
  updateCredentials(patch: Partial<Credentials>): void;
302
+ /**
303
+ * The instance's scoping mode, from whoami. Lives here rather than on the
304
+ * client because every interface factory already receives the transport,
305
+ * and it is per-client, so a module-level global would be wrong the moment a
306
+ * process holds two clients.
307
+ *
308
+ * undefined means the server did not report it, which is true of any
309
+ * deployment older than the field, and must mean "enforce nothing".
310
+ */
311
+ userContextIsolation: string | undefined;
312
+ /** Refuse a customer_id on a B2C instance, at the call site. */
313
+ checkCustomerId(customerId: unknown, where: string): void;
302
314
  setTimeouts(timeouts: Partial<TimeoutConfig>): void;
303
315
  /** `null` restores the default policy, matching Python's `retry_policy=None`. */
304
316
  setRetryPolicy(policy: Partial<RetryPolicy> | null): void;
package/dist/index.js CHANGED
@@ -200,6 +200,16 @@ var Heartbeat = class {
200
200
  }
201
201
  };
202
202
 
203
+ // src/scoping.ts
204
+ var B2C_ISOLATION = "equals_customer";
205
+ function checkCustomerId(isolation, customerId, where, instanceId) {
206
+ if (customerId === void 0 || customerId === null || customerId === "") return;
207
+ if (isolation !== B2C_ISOLATION) return;
208
+ throw new InvalidInputError(
209
+ `${where}: customer_id is not accepted on this instance${instanceId ? ` (${instanceId})` : ""}, which is B2C (user_context_isolation='${B2C_ISOLATION}'). Send user_id only; it is the whole identity, and the server files and reads your data under it. Received customer_id=${JSON.stringify(customerId)}.`
210
+ );
211
+ }
212
+
203
213
  // src/transport/http.ts
204
214
  var DEFAULT_BASE_URL = "https://synap-cloud-prod.maximem.ai";
205
215
  var DEFAULT_TIMEOUTS = { connect: 5, read: 30, write: 10 };
@@ -253,6 +263,25 @@ var HttpTransport = class {
253
263
  updateCredentials(patch) {
254
264
  this.#credentials = { ...this.#credentials, ...patch };
255
265
  }
266
+ /**
267
+ * The instance's scoping mode, from whoami. Lives here rather than on the
268
+ * client because every interface factory already receives the transport,
269
+ * and it is per-client, so a module-level global would be wrong the moment a
270
+ * process holds two clients.
271
+ *
272
+ * undefined means the server did not report it, which is true of any
273
+ * deployment older than the field, and must mean "enforce nothing".
274
+ */
275
+ userContextIsolation = void 0;
276
+ /** Refuse a customer_id on a B2C instance, at the call site. */
277
+ checkCustomerId(customerId, where) {
278
+ checkCustomerId(
279
+ this.userContextIsolation,
280
+ customerId,
281
+ where,
282
+ this.#credentials.instanceId
283
+ );
284
+ }
256
285
  setTimeouts(timeouts) {
257
286
  this.timeouts = { ...this.timeouts, ...timeouts };
258
287
  }
@@ -527,6 +556,10 @@ function buildFetchRequest(scope, options) {
527
556
  }
528
557
  async function fetchContext(transport, scope, options = {}, hooks = {}) {
529
558
  const startedAt = Date.now();
559
+ transport.checkCustomerId(
560
+ opt(options, "customer_id", "customerId"),
561
+ `${scope}.context.fetch`
562
+ );
530
563
  const anticipated = hooks.beforeFetch?.(scope, options) ?? null;
531
564
  if (anticipated !== null) {
532
565
  hooks.onServedFromCache?.(scope, options, anticipated);
@@ -787,6 +820,8 @@ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "error",
787
820
  function createMemoriesInterface(transport) {
788
821
  return {
789
822
  async create(options) {
823
+ const o2 = options;
824
+ transport.checkCustomerId(o2["customer_id"] ?? o2["customerId"], "memories.create");
790
825
  return transport.request("memories_create", { body: buildCreateBody(options) });
791
826
  },
792
827
  async batch_create(options) {
@@ -803,7 +838,7 @@ function createMemoriesInterface(transport) {
803
838
  const userId = options.user_id ?? options.userId;
804
839
  const customerId = options.customer_id ?? options.customerId;
805
840
  if (!userId) throw new InvalidInputError("user_id is required");
806
- if (!customerId) throw new InvalidInputError("customer_id is required");
841
+ transport.checkCustomerId(customerId, "memories.create_from_file");
807
842
  const filePath = options.file_path ?? options.filePath;
808
843
  const sources = [filePath, options.file, options.text].filter((v) => v !== void 0);
809
844
  if (sources.length === 0) {
@@ -946,7 +981,6 @@ function buildRecordMessageBody(options) {
946
981
  throw new InvalidInputError("content is required");
947
982
  }
948
983
  if (!userId) throw new InvalidInputError("user_id is required");
949
- if (!customerId) throw new InvalidInputError("customer_id is required");
950
984
  const body = {
951
985
  conversation_id: conversationId,
952
986
  role: options.role,
@@ -1051,6 +1085,10 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1051
1085
  };
1052
1086
  return {
1053
1087
  async record_message(options) {
1088
+ transport.checkCustomerId(
1089
+ options["customer_id"] ?? options["customerId"],
1090
+ "conversation.record_message"
1091
+ );
1054
1092
  const body = buildRecordMessageBody(options);
1055
1093
  const result = await transport.request("conversations_messages", { body });
1056
1094
  onTurn({
@@ -1069,6 +1107,13 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1069
1107
  if (!Array.isArray(messages) || messages.length === 0) {
1070
1108
  throw new InvalidInputError("messages must be a non-empty array");
1071
1109
  }
1110
+ for (const m of messages) {
1111
+ const mo = m;
1112
+ transport.checkCustomerId(
1113
+ mo["customer_id"] ?? mo["customerId"],
1114
+ "conversation.record_messages_batch"
1115
+ );
1116
+ }
1072
1117
  const bodies = messages.map(buildRecordMessageBody);
1073
1118
  const result = await transport.request("conversations_messages_batch", {
1074
1119
  body: { messages: bodies }
@@ -2377,6 +2422,8 @@ var SynapClient = class {
2377
2422
  const whoami = await this.#transport.request("whoami", {});
2378
2423
  const resolvedClientId = String(whoami["client_id"] ?? "");
2379
2424
  const resolvedInstanceId = String(whoami["instance_id"] ?? "");
2425
+ const iso2 = whoami["user_context_isolation"];
2426
+ this.#transport.userContextIsolation = typeof iso2 === "string" ? iso2 : void 0;
2380
2427
  this.#transport.updateCredentials({
2381
2428
  ...resolvedClientId !== "" && credentials.clientId === "" ? { clientId: resolvedClientId } : {},
2382
2429
  ...resolvedInstanceId !== "" && credentials.instanceId === "" ? { instanceId: resolvedInstanceId } : {}