@maximem/synap-js-sdk 0.5.9 → 0.5.10

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
@@ -326,6 +326,15 @@ declare class HttpTransport {
326
326
  * deployment older than the field, and must mean "enforce nothing".
327
327
  */
328
328
  userContextIsolation: string | undefined;
329
+ /**
330
+ * Set by the client to its identity resolution (`initialize()`'s whoami).
331
+ * Python refuses every call before `initialize()`; here the first request
332
+ * runs it instead, so a caller who skips it gets the same identity-dependent
333
+ * behaviour (client-scope cache hits, the B2C check) rather than an error.
334
+ */
335
+ identityResolver: (() => Promise<void>) | undefined;
336
+ /** Resolve identity once, before the first request. Never throws. */
337
+ ready(): Promise<void>;
329
338
  /** Refuse a customer_id on a B2C instance, at the call site. */
330
339
  checkCustomerId(customerId: unknown, where: string): void;
331
340
  setTimeouts(timeouts: Partial<TimeoutConfig>): void;
@@ -1759,12 +1768,16 @@ declare class SynapClient {
1759
1768
  /**
1760
1769
  * Resolve `client_id` and `instance_id` from the API key.
1761
1770
  *
1762
- * Mirrors Python's `initialize()`. Calling it is optional here (unlike
1763
- * Python, which raises "SDK not initialized" from `_ensure_initialized`)
1764
- * because every HTTP route authenticates on the API key alone. It matters
1765
- * for the anticipation cache: a bundle shared at client scope is filtered on
1766
- * `client_id`, and an empty one fails that filter for every bundle. That was
1767
- * a silent cache-scope bug in Python before whoami bootstrapping existed.
1771
+ * Mirrors Python's `initialize()`. Python raises "SDK not initialized" on
1772
+ * any call made before it; here the first request runs the same resolution
1773
+ * instead (`identityResolver` on the transport), so skipping this call costs
1774
+ * nothing and breaks no existing caller. It matters for the anticipation
1775
+ * cache: a bundle shared at client scope is filtered on `client_id`, and an
1776
+ * empty one fails that filter for every bundle.
1777
+ *
1778
+ * Calling it explicitly still locks `configure()`, as in Python. Resolving
1779
+ * on first use does not, because a caller who never called it could always
1780
+ * configure after a request, and that keeps working.
1768
1781
  *
1769
1782
  * Best-effort and idempotent. A whoami failure leaves whatever the
1770
1783
  * constructor resolved and is not fatal.
@@ -1856,7 +1869,7 @@ declare class SynapClient {
1856
1869
  * build when the two disagree, which is the only thing that makes a manual
1857
1870
  * publish safe.
1858
1871
  */
1859
- declare const SDK_VERSION = "0.5.9";
1872
+ declare const SDK_VERSION = "0.5.10";
1860
1873
 
1861
1874
  /**
1862
1875
  * `response_shape: "ranked"`: one list in the order the server ranked it.
package/dist/index.d.ts CHANGED
@@ -326,6 +326,15 @@ declare class HttpTransport {
326
326
  * deployment older than the field, and must mean "enforce nothing".
327
327
  */
328
328
  userContextIsolation: string | undefined;
329
+ /**
330
+ * Set by the client to its identity resolution (`initialize()`'s whoami).
331
+ * Python refuses every call before `initialize()`; here the first request
332
+ * runs it instead, so a caller who skips it gets the same identity-dependent
333
+ * behaviour (client-scope cache hits, the B2C check) rather than an error.
334
+ */
335
+ identityResolver: (() => Promise<void>) | undefined;
336
+ /** Resolve identity once, before the first request. Never throws. */
337
+ ready(): Promise<void>;
329
338
  /** Refuse a customer_id on a B2C instance, at the call site. */
330
339
  checkCustomerId(customerId: unknown, where: string): void;
331
340
  setTimeouts(timeouts: Partial<TimeoutConfig>): void;
@@ -1759,12 +1768,16 @@ declare class SynapClient {
1759
1768
  /**
1760
1769
  * Resolve `client_id` and `instance_id` from the API key.
1761
1770
  *
1762
- * Mirrors Python's `initialize()`. Calling it is optional here (unlike
1763
- * Python, which raises "SDK not initialized" from `_ensure_initialized`)
1764
- * because every HTTP route authenticates on the API key alone. It matters
1765
- * for the anticipation cache: a bundle shared at client scope is filtered on
1766
- * `client_id`, and an empty one fails that filter for every bundle. That was
1767
- * a silent cache-scope bug in Python before whoami bootstrapping existed.
1771
+ * Mirrors Python's `initialize()`. Python raises "SDK not initialized" on
1772
+ * any call made before it; here the first request runs the same resolution
1773
+ * instead (`identityResolver` on the transport), so skipping this call costs
1774
+ * nothing and breaks no existing caller. It matters for the anticipation
1775
+ * cache: a bundle shared at client scope is filtered on `client_id`, and an
1776
+ * empty one fails that filter for every bundle.
1777
+ *
1778
+ * Calling it explicitly still locks `configure()`, as in Python. Resolving
1779
+ * on first use does not, because a caller who never called it could always
1780
+ * configure after a request, and that keeps working.
1768
1781
  *
1769
1782
  * Best-effort and idempotent. A whoami failure leaves whatever the
1770
1783
  * constructor resolved and is not fatal.
@@ -1856,7 +1869,7 @@ declare class SynapClient {
1856
1869
  * build when the two disagree, which is the only thing that makes a manual
1857
1870
  * publish safe.
1858
1871
  */
1859
- declare const SDK_VERSION = "0.5.9";
1872
+ declare const SDK_VERSION = "0.5.10";
1860
1873
 
1861
1874
  /**
1862
1875
  * `response_shape: "ranked"`: one list in the order the server ranked it.
package/dist/index.js CHANGED
@@ -267,6 +267,17 @@ var HttpTransport = class {
267
267
  * deployment older than the field, and must mean "enforce nothing".
268
268
  */
269
269
  userContextIsolation = void 0;
270
+ /**
271
+ * Set by the client to its identity resolution (`initialize()`'s whoami).
272
+ * Python refuses every call before `initialize()`; here the first request
273
+ * runs it instead, so a caller who skips it gets the same identity-dependent
274
+ * behaviour (client-scope cache hits, the B2C check) rather than an error.
275
+ */
276
+ identityResolver = void 0;
277
+ /** Resolve identity once, before the first request. Never throws. */
278
+ async ready() {
279
+ if (this.identityResolver !== void 0) await this.identityResolver();
280
+ }
270
281
  /** Refuse a customer_id on a B2C instance, at the call site. */
271
282
  checkCustomerId(customerId, where) {
272
283
  checkCustomerId(
@@ -298,6 +309,7 @@ var HttpTransport = class {
298
309
  const spec = ENDPOINTS[name];
299
310
  const correlationId = options.correlationId ?? newCorrelationId();
300
311
  const idempotent = options.idempotent ?? spec.idempotent;
312
+ if (name !== "whoami") await this.ready();
301
313
  let attempt = 0;
302
314
  let last = null;
303
315
  while (attempt < this.retryPolicy.maxAttempts) {
@@ -619,11 +631,10 @@ function buildFetchRequest(scope, options) {
619
631
  const conversationId = opt(options, "conversation_id", "conversationId");
620
632
  const searchQuery = opt(options, "search_query", "searchQuery");
621
633
  const maxResults = opt(options, "max_results", "maxResults") ?? 10;
622
- const precisionLevel = opt(options, "precision_level", "precisionLevel") ?? "high";
623
634
  const queryDate = opt(options, "query_date", "queryDate");
624
635
  const perQuery = opt(options, "max_results_per_query", "maxResultsPerQuery");
625
636
  const responseShape = responseShapeOf(options);
626
- const mode = options.mode ?? "fast";
637
+ const { mode, precisionLevel } = retrievalOptionsOf(options);
627
638
  if (searchQuery !== void 0 && !Array.isArray(searchQuery)) {
628
639
  throw new InvalidInputError("search_query must be an array when provided");
629
640
  }
@@ -644,13 +655,15 @@ function buildFetchRequest(scope, options) {
644
655
  if (options.include_conversation_context === false) {
645
656
  body["include_conversation_context"] = false;
646
657
  }
658
+ const scopePath = opt(options, "scope_path", "scopePath");
659
+ if (scopePath !== void 0 && scopePath !== null && Object.keys(scopePath).length > 0) {
660
+ body["scope"] = scopePath;
661
+ }
647
662
  switch (scope) {
648
663
  case "user":
649
664
  {
650
665
  if (!userId) throw new InvalidInputError("user_id is required");
651
666
  body["user_id"] = userId;
652
- const scopePath = opt(options, "scope_path", "scopePath");
653
- if (scopePath !== void 0) body["scope"] = scopePath;
654
667
  const contextMode = opt(options, "context_mode", "contextMode") ?? "in-conversation";
655
668
  if (!VALID_CONTEXT_MODES.includes(contextMode)) {
656
669
  throw new InvalidInputError(
@@ -697,13 +710,32 @@ function responseShapeOf(options) {
697
710
  }
698
711
  return shape;
699
712
  }
713
+ var VALID_MODES = ["fast", "accurate"];
714
+ var VALID_PRECISION = ["high", "medium"];
715
+ function retrievalOptionsOf(options) {
716
+ const mode = options.mode ?? "fast";
717
+ if (!VALID_MODES.includes(mode)) {
718
+ throw new InvalidInputError(
719
+ `Invalid mode '${mode}'. Must be one of: ${VALID_MODES.join(", ")}`
720
+ );
721
+ }
722
+ const precisionLevel = opt(options, "precision_level", "precisionLevel") ?? "high";
723
+ if (!VALID_PRECISION.includes(precisionLevel)) {
724
+ throw new InvalidInputError(
725
+ `Invalid precision_level '${precisionLevel}'. Must be one of: ${VALID_PRECISION.join(", ")}`
726
+ );
727
+ }
728
+ return { mode, precisionLevel };
729
+ }
700
730
  async function fetchContext(transport, scope, options = {}, hooks = {}) {
701
731
  const startedAt = Date.now();
732
+ await transport.ready?.();
702
733
  transport.checkCustomerId(
703
734
  opt(options, "customer_id", "customerId"),
704
735
  `${scope}.context.fetch`
705
736
  );
706
737
  const responseShape = responseShapeOf(options);
738
+ retrievalOptionsOf(options);
707
739
  const network = async () => {
708
740
  const { endpoint, body } = buildFetchRequest(scope, options);
709
741
  const result = await transport.request(endpoint, { body });
@@ -1922,7 +1954,7 @@ function createInstanceNamespace(credentials, cache, onTurn, agentModel = () =>
1922
1954
  }
1923
1955
 
1924
1956
  // src/version.ts
1925
- var SDK_VERSION = "0.5.9";
1957
+ var SDK_VERSION = "0.5.10";
1926
1958
 
1927
1959
  // src/registry.ts
1928
1960
  var instances = /* @__PURE__ */ new Map();
@@ -3648,6 +3680,7 @@ var SynapClient = class _SynapClient {
3648
3680
  instance;
3649
3681
  #closed = false;
3650
3682
  #initialized = false;
3683
+ #identity;
3651
3684
  #registryKeys;
3652
3685
  #compactionSubscribers;
3653
3686
  #shortTerm;
@@ -3698,6 +3731,7 @@ var SynapClient = class _SynapClient {
3698
3731
  userAgent: `@maximem/synap-js-sdk/${SDK_VERSION}`
3699
3732
  });
3700
3733
  if (options.heartbeat) this.#transport.startHeartbeat();
3734
+ this.#transport.identityResolver = () => this.#resolveIdentity();
3701
3735
  this.#anticipationCache = new AnticipationCache();
3702
3736
  this.#fetchHooks = {
3703
3737
  beforeFetch: async (scope, options2) => {
@@ -3903,19 +3937,30 @@ var SynapClient = class _SynapClient {
3903
3937
  /**
3904
3938
  * Resolve `client_id` and `instance_id` from the API key.
3905
3939
  *
3906
- * Mirrors Python's `initialize()`. Calling it is optional here (unlike
3907
- * Python, which raises "SDK not initialized" from `_ensure_initialized`)
3908
- * because every HTTP route authenticates on the API key alone. It matters
3909
- * for the anticipation cache: a bundle shared at client scope is filtered on
3910
- * `client_id`, and an empty one fails that filter for every bundle. That was
3911
- * a silent cache-scope bug in Python before whoami bootstrapping existed.
3940
+ * Mirrors Python's `initialize()`. Python raises "SDK not initialized" on
3941
+ * any call made before it; here the first request runs the same resolution
3942
+ * instead (`identityResolver` on the transport), so skipping this call costs
3943
+ * nothing and breaks no existing caller. It matters for the anticipation
3944
+ * cache: a bundle shared at client scope is filtered on `client_id`, and an
3945
+ * empty one fails that filter for every bundle.
3946
+ *
3947
+ * Calling it explicitly still locks `configure()`, as in Python. Resolving
3948
+ * on first use does not, because a caller who never called it could always
3949
+ * configure after a request, and that keeps working.
3912
3950
  *
3913
3951
  * Best-effort and idempotent. A whoami failure leaves whatever the
3914
3952
  * constructor resolved and is not fatal.
3915
3953
  */
3916
3954
  async initialize() {
3917
- if (this.#initialized) return;
3918
3955
  this.#initialized = true;
3956
+ await this.#resolveIdentity();
3957
+ }
3958
+ /** Runs whoami at most once, however many calls arrive together. */
3959
+ #resolveIdentity() {
3960
+ this.#identity ??= this.#whoami();
3961
+ return this.#identity;
3962
+ }
3963
+ async #whoami() {
3919
3964
  const credentials = this.#transport.currentCredentials();
3920
3965
  if (credentials.clientId !== "" && credentials.instanceId !== "") return;
3921
3966
  try {