@maximem/synap-js-sdk 0.5.12 → 0.5.13

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
@@ -949,6 +949,16 @@ interface WaitOptions {
949
949
  signal?: AbortSignal;
950
950
  }
951
951
 
952
+ /**
953
+ * Scope-aware context fetching.
954
+ *
955
+ * Scopes are client > customer > user. `conversation_id` is NOT a tier: it
956
+ * groups turns within a scope. Passing it narrows an existing scope rather
957
+ * than selecting a different one.
958
+ */
959
+
960
+ type Scope = 'user' | 'customer' | 'client' | 'conversation';
961
+
952
962
  /**
953
963
  * Conversation namespace. Mirrors `ConversationInterface` and
954
964
  * `ConversationContextInterface` in the Python SDK.
@@ -1106,16 +1116,6 @@ type WriteInvalidator = (ids: {
1106
1116
  customerId?: string | undefined;
1107
1117
  }) => void;
1108
1118
 
1109
- /**
1110
- * Scope-aware context fetching.
1111
- *
1112
- * Scopes are client > customer > user. `conversation_id` is NOT a tier: it
1113
- * groups turns within a scope. Passing it narrows an existing scope rather
1114
- * than selecting a different one.
1115
- */
1116
-
1117
- type Scope = 'user' | 'customer' | 'client' | 'conversation';
1118
-
1119
1119
  /** User namespace. Mirrors `UserInterface` in the Python SDK. */
1120
1120
 
1121
1121
  interface UserNamespace {
@@ -1873,7 +1873,7 @@ declare class SynapClient {
1873
1873
  * build when the two disagree, which is the only thing that makes a manual
1874
1874
  * publish safe.
1875
1875
  */
1876
- declare const SDK_VERSION = "0.5.12";
1876
+ declare const SDK_VERSION = "0.5.13";
1877
1877
 
1878
1878
  /**
1879
1879
  * `response_shape: "ranked"`: one list in the order the server ranked it.
package/dist/index.d.ts CHANGED
@@ -949,6 +949,16 @@ interface WaitOptions {
949
949
  signal?: AbortSignal;
950
950
  }
951
951
 
952
+ /**
953
+ * Scope-aware context fetching.
954
+ *
955
+ * Scopes are client > customer > user. `conversation_id` is NOT a tier: it
956
+ * groups turns within a scope. Passing it narrows an existing scope rather
957
+ * than selecting a different one.
958
+ */
959
+
960
+ type Scope = 'user' | 'customer' | 'client' | 'conversation';
961
+
952
962
  /**
953
963
  * Conversation namespace. Mirrors `ConversationInterface` and
954
964
  * `ConversationContextInterface` in the Python SDK.
@@ -1106,16 +1116,6 @@ type WriteInvalidator = (ids: {
1106
1116
  customerId?: string | undefined;
1107
1117
  }) => void;
1108
1118
 
1109
- /**
1110
- * Scope-aware context fetching.
1111
- *
1112
- * Scopes are client > customer > user. `conversation_id` is NOT a tier: it
1113
- * groups turns within a scope. Passing it narrows an existing scope rather
1114
- * than selecting a different one.
1115
- */
1116
-
1117
- type Scope = 'user' | 'customer' | 'client' | 'conversation';
1118
-
1119
1119
  /** User namespace. Mirrors `UserInterface` in the Python SDK. */
1120
1120
 
1121
1121
  interface UserNamespace {
@@ -1873,7 +1873,7 @@ declare class SynapClient {
1873
1873
  * build when the two disagree, which is the only thing that makes a manual
1874
1874
  * publish safe.
1875
1875
  */
1876
- declare const SDK_VERSION = "0.5.12";
1876
+ declare const SDK_VERSION = "0.5.13";
1877
1877
 
1878
1878
  /**
1879
1879
  * `response_shape: "ranked"`: one list in the order the server ranked it.
package/dist/index.js CHANGED
@@ -408,13 +408,25 @@ function isOutcomeUnknown(error) {
408
408
  if (error instanceof ConflictError) return false;
409
409
  if (error instanceof InsufficientCreditsError) return false;
410
410
  if (error instanceof ContextNotFoundError) return false;
411
- if (error instanceof NetworkTimeoutError) return true;
412
411
  if (isConnectionRefusal(error)) return false;
412
+ if (error instanceof NetworkTimeoutError) return true;
413
413
  return true;
414
414
  }
415
+ var CONNECTION_REFUSAL_CODES = /* @__PURE__ */ new Set([
416
+ "ECONNREFUSED",
417
+ "ENOTFOUND",
418
+ "EAI_AGAIN",
419
+ "ECONNRESET",
420
+ "EHOSTUNREACH"
421
+ ]);
415
422
  function isConnectionRefusal(error) {
416
- const code = error?.cause?.code ?? error?.code;
417
- return code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "EAI_AGAIN" || code === "ECONNRESET" || code === "EHOSTUNREACH";
423
+ let cur = error;
424
+ for (let depth = 0; depth < 4 && cur !== null && typeof cur === "object"; depth += 1) {
425
+ const code = cur.code;
426
+ if (typeof code === "string" && CONNECTION_REFUSAL_CODES.has(code)) return true;
427
+ cur = cur.cause;
428
+ }
429
+ return false;
418
430
  }
419
431
  function toTransportError(error, correlationId) {
420
432
  if (error instanceof SynapError) return error;
@@ -422,6 +434,12 @@ function toTransportError(error, correlationId) {
422
434
  if (name === "AbortError" || name === "TimeoutError") {
423
435
  return new NetworkTimeoutError("Request timed out", { correlationId, cause: error });
424
436
  }
437
+ if (isConnectionRefusal(error)) {
438
+ return new NetworkTimeoutError(
439
+ `Connection failed: ${error?.message ?? String(error)}`,
440
+ { correlationId, cause: error }
441
+ );
442
+ }
425
443
  return new ServiceUnavailableError(
426
444
  `Network request failed: ${error?.message ?? String(error)}`,
427
445
  { correlationId, cause: error }
@@ -758,8 +776,13 @@ async function fetchContext(transport, scope, options = {}, hooks = {}) {
758
776
  hooks.onResponse?.(scope, options, response2);
759
777
  return response2;
760
778
  };
779
+ const conversationId = opt(options, "conversation_id", "conversationId");
780
+ const skipServerSt = conversationId ? hooks.skipServerShortTerm?.(conversationId) === true : false;
761
781
  const network = async () => {
762
- const { endpoint, body } = buildFetchRequest(scope, options);
782
+ const { endpoint, body } = buildFetchRequest(
783
+ scope,
784
+ skipServerSt ? { ...options, include_conversation_context: false } : options
785
+ );
763
786
  const result = await transport.request(endpoint, { body });
764
787
  return applyResponseShape(result?.["context"] ?? {}, responseShape);
765
788
  };
@@ -1420,7 +1443,7 @@ function storedBatchRows(bodies, result) {
1420
1443
  function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
1421
1444
  }, onTurn = () => {
1422
1445
  }, onFetched = () => {
1423
- }, localShortTerm = { prompt: () => null, compacted: () => null }, agentModel = () => ({})) {
1446
+ }, localShortTerm = { prompt: () => null, compacted: () => null }, agentModel = () => ({}), fetchHooks = {}) {
1424
1447
  const writesInFlight = /* @__PURE__ */ new Map();
1425
1448
  const withTurnModel = (body, options) => {
1426
1449
  const metadata = withAgentModel(
@@ -1527,7 +1550,7 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1527
1550
  async fetch(options = {}) {
1528
1551
  const id = options.conversation_id ?? options.conversationId;
1529
1552
  validateConversationId(id);
1530
- const response = await fetchContext(transport, "conversation", options);
1553
+ const response = await fetchContext(transport, "conversation", options, fetchHooks);
1531
1554
  onFetched(
1532
1555
  response,
1533
1556
  id,
@@ -1976,7 +1999,7 @@ function createInstanceNamespace(credentials, cache, onTurn, agentModel = () =>
1976
1999
  }
1977
2000
 
1978
2001
  // src/version.ts
1979
- var SDK_VERSION = "0.5.12";
2002
+ var SDK_VERSION = "0.5.13";
1980
2003
 
1981
2004
  // src/registry.ts
1982
2005
  var instances = /* @__PURE__ */ new Map();
@@ -3799,6 +3822,14 @@ var SynapClient = class _SynapClient {
3799
3822
  // user, customer and client fetches (the conversation fetch does its own
3800
3823
  // through `onFetched`). Until 0.5.12 only the conversation fetch had it,
3801
3824
  // so the same usage gave the model different context in JS.
3825
+ // Python's `_should_skip_server_st`: when this process already holds the
3826
+ // conversation's compaction and every turn after it, the server is told
3827
+ // not to assemble them again.
3828
+ skipServerShortTerm: (conversationId) => {
3829
+ if (!this.#isStAuthoritative()) return false;
3830
+ const entry = this.#shortTerm.get(conversationId);
3831
+ return entry !== null && entry.compactionId !== null && isComplete(entry);
3832
+ },
3802
3833
  onResponse: (scope, options2, response) => {
3803
3834
  if (scope === "conversation") return;
3804
3835
  const o2 = options2;
@@ -3862,7 +3893,12 @@ var SynapClient = class _SynapClient {
3862
3893
  reseed: (conversationId, response) => this.#reseedShortTerm(conversationId, response),
3863
3894
  historyExcerpts: (conversationId, response, query) => this.#withHistoryExcerpts(conversationId, response, query)
3864
3895
  },
3865
- () => this.#agentModel
3896
+ () => this.#agentModel,
3897
+ // The same hooks as the other three scopes: the anticipation cache first,
3898
+ // as Python's conversation fetch does, and the audit events. Until 0.5.13
3899
+ // the conversation fetch was built without them, so every JS conversation
3900
+ // fetch was a billed round trip, including ones the stream had answered.
3901
+ this.#fetchHooks
3866
3902
  );
3867
3903
  this.memories = createMemoriesInterface(this.#transport, ({ userId, customerId }) => {
3868
3904
  for (const id of /* @__PURE__ */ new Set([userId, customerId])) {
@@ -4476,7 +4512,7 @@ function resolveBaseUrl(options) {
4476
4512
  function resolveCredentials(options) {
4477
4513
  const apiKey = options.apiKey ?? getEnv("SYNAP_API_KEY");
4478
4514
  if (!apiKey) {
4479
- throw new InvalidInputError(
4515
+ throw new AuthenticationError(
4480
4516
  "No Synap API key found. Set SYNAP_API_KEY in your environment, or pass apiKey to the SynapClient constructor."
4481
4517
  );
4482
4518
  }