@maximem/synap-js-sdk 0.5.12 → 0.5.14

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.cjs CHANGED
@@ -408,22 +408,40 @@ function isOutcomeUnknown(error) {
408
408
  if (error instanceof _chunkUXNVTP4Dcjs.ConflictError) return false;
409
409
  if (error instanceof _chunkUXNVTP4Dcjs.InsufficientCreditsError) return false;
410
410
  if (error instanceof _chunkUXNVTP4Dcjs.ContextNotFoundError) return false;
411
- if (error instanceof _chunkUXNVTP4Dcjs.NetworkTimeoutError) return true;
412
411
  if (isConnectionRefusal(error)) return false;
412
+ if (error instanceof _chunkUXNVTP4Dcjs.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 = _nullishCoalesce(_optionalChain([error, 'optionalAccess', _10 => _10.cause, 'optionalAccess', _11 => _11.code]), () => ( _optionalChain([error, 'optionalAccess', _12 => _12.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 _chunkUXNVTP4Dcjs.SynapError) return error;
421
- const name = _optionalChain([error, 'optionalAccess', _13 => _13.name]);
433
+ const name = _optionalChain([error, 'optionalAccess', _10 => _10.name]);
422
434
  if (name === "AbortError" || name === "TimeoutError") {
423
435
  return new (0, _chunkUXNVTP4Dcjs.NetworkTimeoutError)("Request timed out", { correlationId, cause: error });
424
436
  }
437
+ if (isConnectionRefusal(error)) {
438
+ return new (0, _chunkUXNVTP4Dcjs.NetworkTimeoutError)(
439
+ `Connection failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _11 => _11.message]), () => ( String(error)))}`,
440
+ { correlationId, cause: error }
441
+ );
442
+ }
425
443
  return new (0, _chunkUXNVTP4Dcjs.ServiceUnavailableError)(
426
- `Network request failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _14 => _14.message]), () => ( String(error)))}`,
444
+ `Network request failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _12 => _12.message]), () => ( String(error)))}`,
427
445
  { correlationId, cause: error }
428
446
  );
429
447
  }
@@ -493,6 +511,7 @@ async function toHttpError(response, correlationId) {
493
511
  return new (0, _chunkUXNVTP4Dcjs.AgentUnavailableError)(message, opts);
494
512
  default:
495
513
  if (response.status >= 500) return new (0, _chunkUXNVTP4Dcjs.ServiceUnavailableError)(message, opts);
514
+ if (response.status >= 400) return new (0, _chunkUXNVTP4Dcjs.SynapPermanentError)(message, opts);
496
515
  return new (0, _chunkUXNVTP4Dcjs.SynapError)(message, opts);
497
516
  }
498
517
  }
@@ -504,11 +523,11 @@ function stringOrNull(v) {
504
523
  }
505
524
  function withTimeout(ms, external) {
506
525
  const controller = new AbortController();
507
- const onExternalAbort = () => controller.abort(_optionalChain([external, 'optionalAccess', _15 => _15.reason]));
526
+ const onExternalAbort = () => controller.abort(_optionalChain([external, 'optionalAccess', _13 => _13.reason]));
508
527
  const timer = setTimeout(() => {
509
528
  controller.abort(new (0, _chunkUXNVTP4Dcjs.NetworkTimeoutError)(`Timed out after ${ms}ms`));
510
529
  }, ms);
511
- _optionalChain([timer, 'access', _16 => _16.unref, 'optionalCall', _17 => _17()]);
530
+ _optionalChain([timer, 'access', _14 => _14.unref, 'optionalCall', _15 => _15()]);
512
531
  if (external) {
513
532
  if (external.aborted) onExternalAbort();
514
533
  else external.addEventListener("abort", onExternalAbort, { once: true });
@@ -517,7 +536,7 @@ function withTimeout(ms, external) {
517
536
  signal: controller.signal,
518
537
  dispose() {
519
538
  clearTimeout(timer);
520
- _optionalChain([external, 'optionalAccess', _18 => _18.removeEventListener, 'call', _19 => _19("abort", onExternalAbort)]);
539
+ _optionalChain([external, 'optionalAccess', _16 => _16.removeEventListener, 'call', _17 => _17("abort", onExternalAbort)]);
521
540
  }
522
541
  };
523
542
  }
@@ -525,14 +544,14 @@ function sleep(ms, signal) {
525
544
  if (ms <= 0) return Promise.resolve();
526
545
  return new Promise((resolve, reject) => {
527
546
  const timer = setTimeout(() => {
528
- _optionalChain([signal, 'optionalAccess', _20 => _20.removeEventListener, 'call', _21 => _21("abort", onAbort)]);
547
+ _optionalChain([signal, 'optionalAccess', _18 => _18.removeEventListener, 'call', _19 => _19("abort", onAbort)]);
529
548
  resolve();
530
549
  }, ms);
531
550
  function onAbort() {
532
551
  clearTimeout(timer);
533
- reject(_nullishCoalesce(_optionalChain([signal, 'optionalAccess', _22 => _22.reason]), () => ( new Error("Aborted"))));
552
+ reject(_nullishCoalesce(_optionalChain([signal, 'optionalAccess', _20 => _20.reason]), () => ( new Error("Aborted"))));
534
553
  }
535
- _optionalChain([signal, 'optionalAccess', _23 => _23.addEventListener, 'call', _24 => _24("abort", onAbort, { once: true })]);
554
+ _optionalChain([signal, 'optionalAccess', _21 => _21.addEventListener, 'call', _22 => _22("abort", onAbort, { once: true })]);
536
555
  });
537
556
  }
538
557
 
@@ -745,7 +764,7 @@ function retrievalOptionsOf(options) {
745
764
  }
746
765
  async function fetchContext(transport, scope, options = {}, hooks = {}) {
747
766
  const startedAt = Date.now();
748
- await _optionalChain([transport, 'access', _25 => _25.ready, 'optionalCall', _26 => _26()]);
767
+ await _optionalChain([transport, 'access', _23 => _23.ready, 'optionalCall', _24 => _24()]);
749
768
  await transport.checkCustomerId(
750
769
  opt(options, "customer_id", "customerId"),
751
770
  `${scope}.context.fetch`
@@ -755,11 +774,16 @@ async function fetchContext(transport, scope, options = {}, hooks = {}) {
755
774
  const { contextMode } = scopeOptionsOf(scope, options);
756
775
  const summaryMode = scope === "user" && contextMode === "conversation-summary";
757
776
  const finish = (response2) => {
758
- _optionalChain([hooks, 'access', _27 => _27.onResponse, 'optionalCall', _28 => _28(scope, options, response2)]);
777
+ _optionalChain([hooks, 'access', _25 => _25.onResponse, 'optionalCall', _26 => _26(scope, options, response2)]);
759
778
  return response2;
760
779
  };
780
+ const conversationId = opt(options, "conversation_id", "conversationId");
781
+ const skipServerSt = conversationId ? _optionalChain([hooks, 'access', _27 => _27.skipServerShortTerm, 'optionalCall', _28 => _28(conversationId)]) === true : false;
761
782
  const network = async () => {
762
- const { endpoint, body } = buildFetchRequest(scope, options);
783
+ const { endpoint, body } = buildFetchRequest(
784
+ scope,
785
+ skipServerSt ? { ...options, include_conversation_context: false } : options
786
+ );
763
787
  const result = await transport.request(endpoint, { body });
764
788
  return applyResponseShape(_nullishCoalesce(_optionalChain([result, 'optionalAccess', _29 => _29["context"]]), () => ( {})), responseShape);
765
789
  };
@@ -1417,10 +1441,11 @@ function storedBatchRows(bodies, result) {
1417
1441
  });
1418
1442
  return pairs;
1419
1443
  }
1444
+ var PROMPT_STYLES = ["structured", "narrative", "bullet_points"];
1420
1445
  function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
1421
1446
  }, onTurn = () => {
1422
1447
  }, onFetched = () => {
1423
- }, localShortTerm = { prompt: () => null, compacted: () => null }, agentModel = () => ({})) {
1448
+ }, localShortTerm = { prompt: () => null, compacted: () => null }, agentModel = () => ({}), fetchHooks = {}) {
1424
1449
  const writesInFlight = /* @__PURE__ */ new Map();
1425
1450
  const withTurnModel = (body, options) => {
1426
1451
  const metadata = withAgentModel(
@@ -1527,7 +1552,7 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1527
1552
  async fetch(options = {}) {
1528
1553
  const id = _nullishCoalesce(options.conversation_id, () => ( options.conversationId));
1529
1554
  validateConversationId(id);
1530
- const response = await fetchContext(transport, "conversation", options);
1555
+ const response = await fetchContext(transport, "conversation", options, fetchHooks);
1531
1556
  onFetched(
1532
1557
  response,
1533
1558
  id,
@@ -1538,6 +1563,12 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1538
1563
  },
1539
1564
  get_context_for_prompt: async (options = {}) => {
1540
1565
  const id = convId(options);
1566
+ const style = _nullishCoalesce(options.style, () => ( "structured"));
1567
+ if (!PROMPT_STYLES.includes(style)) {
1568
+ throw new (0, _chunkUXNVTP4Dcjs.InvalidInputError)(
1569
+ `Invalid style '${style}'. Must be one of: ${PROMPT_STYLES.join(", ")}`
1570
+ );
1571
+ }
1541
1572
  const search = (_nullishCoalesce(_nullishCoalesce(options.search_history, () => ( options.searchHistory)), () => ( true))) && localShortTerm.historyExcerpts;
1542
1573
  const withHistory = async (response2) => {
1543
1574
  if (!search) return response2;
@@ -1547,11 +1578,11 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1547
1578
  return response2;
1548
1579
  }
1549
1580
  };
1550
- const local = localShortTerm.prompt(id, _nullishCoalesce(options.style, () => ( "structured")));
1581
+ const local = localShortTerm.prompt(id, style);
1551
1582
  if (local !== null) return withHistory(local);
1552
1583
  const result = await transport.request("context_for_prompt", {
1553
1584
  pathParams: { conversation_id: id },
1554
- query: { style: _nullishCoalesce(options.style, () => ( "structured")) }
1585
+ query: { style }
1555
1586
  });
1556
1587
  const response = _nullishCoalesce(_nullishCoalesce(_optionalChain([result, 'optionalAccess', _50 => _50["context_for_prompt"]]), () => ( result)), () => ( {}));
1557
1588
  _optionalChain([localShortTerm, 'access', _51 => _51.reseed, 'optionalCall', _52 => _52(id, response)]);
@@ -1976,7 +2007,7 @@ function createInstanceNamespace(credentials, cache, onTurn, agentModel = () =>
1976
2007
  }
1977
2008
 
1978
2009
  // src/version.ts
1979
- var SDK_VERSION = "0.5.12";
2010
+ var SDK_VERSION = "0.5.14";
1980
2011
 
1981
2012
  // src/registry.ts
1982
2013
  var instances = /* @__PURE__ */ new Map();
@@ -3799,6 +3830,14 @@ var SynapClient = (_class4 = class _SynapClient {
3799
3830
  // user, customer and client fetches (the conversation fetch does its own
3800
3831
  // through `onFetched`). Until 0.5.12 only the conversation fetch had it,
3801
3832
  // so the same usage gave the model different context in JS.
3833
+ // Python's `_should_skip_server_st`: when this process already holds the
3834
+ // conversation's compaction and every turn after it, the server is told
3835
+ // not to assemble them again.
3836
+ skipServerShortTerm: (conversationId) => {
3837
+ if (!this.#isStAuthoritative()) return false;
3838
+ const entry = this.#shortTerm.get(conversationId);
3839
+ return entry !== null && entry.compactionId !== null && isComplete(entry);
3840
+ },
3802
3841
  onResponse: (scope, options2, response) => {
3803
3842
  if (scope === "conversation") return;
3804
3843
  const o2 = options2;
@@ -3862,7 +3901,12 @@ var SynapClient = (_class4 = class _SynapClient {
3862
3901
  reseed: (conversationId, response) => this.#reseedShortTerm(conversationId, response),
3863
3902
  historyExcerpts: (conversationId, response, query) => this.#withHistoryExcerpts(conversationId, response, query)
3864
3903
  },
3865
- () => this.#agentModel
3904
+ () => this.#agentModel,
3905
+ // The same hooks as the other three scopes: the anticipation cache first,
3906
+ // as Python's conversation fetch does, and the audit events. Until 0.5.13
3907
+ // the conversation fetch was built without them, so every JS conversation
3908
+ // fetch was a billed round trip, including ones the stream had answered.
3909
+ this.#fetchHooks
3866
3910
  );
3867
3911
  this.memories = createMemoriesInterface(this.#transport, ({ userId, customerId }) => {
3868
3912
  for (const id of /* @__PURE__ */ new Set([userId, customerId])) {
@@ -4476,7 +4520,7 @@ function resolveBaseUrl(options) {
4476
4520
  function resolveCredentials(options) {
4477
4521
  const apiKey = _nullishCoalesce(options.apiKey, () => ( _chunkZKOJDUN4cjs.getEnv.call(void 0, "SYNAP_API_KEY")));
4478
4522
  if (!apiKey) {
4479
- throw new (0, _chunkUXNVTP4Dcjs.InvalidInputError)(
4523
+ throw new (0, _chunkUXNVTP4Dcjs.AuthenticationError)(
4480
4524
  "No Synap API key found. Set SYNAP_API_KEY in your environment, or pass apiKey to the SynapClient constructor."
4481
4525
  );
4482
4526
  }