@maximem/synap-js-sdk 0.4.4 → 0.4.6

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
@@ -212,7 +212,12 @@ function checkCustomerId(isolation, customerId, where, instanceId) {
212
212
 
213
213
  // src/transport/http.ts
214
214
  var DEFAULT_BASE_URL = "https://synap-cloud-prod.maximem.ai";
215
- var DEFAULT_TIMEOUTS = { connect: 5, read: 30, write: 10 };
215
+ var DEFAULT_TIMEOUTS = {
216
+ connect: 5,
217
+ read: 30,
218
+ write: 10,
219
+ streamIdle: 60
220
+ };
216
221
  var HttpTransport = (_class2 = class {
217
222
 
218
223
  // Mutable so `configure()` can adjust them before the first request, and so
@@ -1121,7 +1126,7 @@ var CompactionSubscribers = class {
1121
1126
  function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
1122
1127
  }, onTurn = () => {
1123
1128
  }, onFetched = () => {
1124
- }) {
1129
+ }, localShortTerm = { prompt: () => null, compacted: () => null }) {
1125
1130
  const convId = (v) => {
1126
1131
  const id = typeof v === "string" ? v : _nullishCoalesce(v.conversation_id, () => ( v.conversationId));
1127
1132
  if (!id) throw new (0, _chunk2TVL5IPQcjs.InvalidInputError)("conversation_id is required");
@@ -1202,6 +1207,8 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1202
1207
  },
1203
1208
  get_context_for_prompt: async (options = {}) => {
1204
1209
  const id = convId(options);
1210
+ const local = localShortTerm.prompt(id, _nullishCoalesce(options.style, () => ( "structured")));
1211
+ if (local !== null) return local;
1205
1212
  const result = await transport.request("context_for_prompt", {
1206
1213
  pathParams: { conversation_id: id },
1207
1214
  query: { style: _nullishCoalesce(options.style, () => ( "structured")) }
@@ -1213,6 +1220,10 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1213
1220
  },
1214
1221
  get_compacted: async (options) => {
1215
1222
  const id = convId(options);
1223
+ if (options.version === void 0) {
1224
+ const local = localShortTerm.compacted(id, _nullishCoalesce(options.format, () => ( "structured")));
1225
+ if (local !== null) return local;
1226
+ }
1216
1227
  const query = { format: _nullishCoalesce(options.format, () => ( "structured")) };
1217
1228
  if (options.version !== void 0) query["version"] = options.version;
1218
1229
  try {
@@ -1343,6 +1354,18 @@ function envTls() {
1343
1354
  if (raw === void 0 || raw === "") return void 0;
1344
1355
  return !["0", "false", "no", "off"].includes(raw.trim().toLowerCase());
1345
1356
  }
1357
+ function thinkingMetadata(options) {
1358
+ const md = { ..._nullishCoalesce(options.metadata, () => ( {})) };
1359
+ const step = _nullishCoalesce(options.step_index, () => ( options.stepIndex));
1360
+ if (step !== void 0 && md["step_index"] === void 0) {
1361
+ md["step_index"] = String(step);
1362
+ }
1363
+ const kind = _nullishCoalesce(options.thought_type, () => ( options.thoughtType));
1364
+ if (kind && md["thought_type"] === void 0) {
1365
+ md["thought_type"] = kind;
1366
+ }
1367
+ return md;
1368
+ }
1346
1369
  function createInstanceNamespace(credentials, cache, onTurn) {
1347
1370
  let client = null;
1348
1371
  let dispatchCompaction = null;
@@ -1454,7 +1477,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1454
1477
  user_id: _nullishCoalesce(_nullishCoalesce(options.user_id, () => ( options.userId)), () => ( "")),
1455
1478
  customer_id: _nullishCoalesce(_nullishCoalesce(options.customer_id, () => ( options.customerId)), () => ( "")),
1456
1479
  session_id: _nullishCoalesce(_nullishCoalesce(options.session_id, () => ( options.sessionId)), () => ( "")),
1457
- metadata: _nullishCoalesce(options.metadata, () => ( {})),
1480
+ metadata: thinkingMetadata(options),
1458
1481
  timestamp_ms: Date.now()
1459
1482
  });
1460
1483
  },
@@ -1637,11 +1660,11 @@ var ShortTermStore = class {
1637
1660
  // src/context/overlay.ts
1638
1661
  var MAX_TURNS = 20;
1639
1662
  var TOKEN_BUDGET = 2e3;
1640
- function verbatimOverlayEnabled() {
1663
+ function verbatimOverlayEnabled(configured) {
1641
1664
  const raw = (_nullishCoalesce(_chunkZKOJDUN4cjs.getEnv.call(void 0, "SYNAP_ST_VERBATIM_OVERLAY"), () => ( ""))).trim().toLowerCase();
1642
1665
  if (["0", "false", "no", "off"].includes(raw)) return false;
1643
1666
  if (["1", "true", "yes", "on"].includes(raw)) return true;
1644
- return true;
1667
+ return _nullishCoalesce(configured, () => ( true));
1645
1668
  }
1646
1669
  function estimateTokens(turn) {
1647
1670
  return Math.max(1, Math.floor(String(_nullishCoalesce(turn.content, () => ( ""))).length / 4));
@@ -1655,13 +1678,13 @@ function budgetRecentTurns(turns) {
1655
1678
  }
1656
1679
  return windowed;
1657
1680
  }
1658
- function overlayLocalRecentTurns(response, store, conversationId) {
1681
+ function overlayLocalRecentTurns(response, store, conversationId, configured) {
1659
1682
  if (conversationId === void 0 || conversationId === "") return;
1660
1683
  try {
1661
1684
  const entry = store.get(conversationId);
1662
1685
  if (entry === null) return;
1663
1686
  const existing = _nullishCoalesce(response.conversation_context, () => ( null));
1664
- if (existing !== null && !verbatimOverlayEnabled()) return;
1687
+ if (existing !== null && !verbatimOverlayEnabled(configured)) return;
1665
1688
  const localTurns = budgetRecentTurns(entry.recentTurns);
1666
1689
  const serverField = (name) => existing === null ? void 0 : existing[name];
1667
1690
  const orLocal = (name, local) => {
@@ -1683,6 +1706,158 @@ function overlayLocalRecentTurns(response, store, conversationId) {
1683
1706
  }
1684
1707
  }
1685
1708
 
1709
+ // src/context/local-prompt.ts
1710
+ var SUPPORTED_STYLES = ["structured", "narrative", "bullet_points"];
1711
+ var DEFAULT_STYLE = "structured";
1712
+ function stringify(value) {
1713
+ if (value === null || value === void 0) return "";
1714
+ const t = typeof value;
1715
+ if (t === "string" || t === "number" || t === "boolean") return String(value);
1716
+ try {
1717
+ return _nullishCoalesce(JSON.stringify(value), () => ( ""));
1718
+ } catch (e6) {
1719
+ return String(value);
1720
+ }
1721
+ }
1722
+ function asListOfObjects(value) {
1723
+ if (!value) return [];
1724
+ if (Array.isArray(value)) {
1725
+ const out = [];
1726
+ for (const v of value) {
1727
+ if (v && typeof v === "object" && !Array.isArray(v)) out.push(v);
1728
+ else if (typeof v === "string") out.push({ content: v });
1729
+ }
1730
+ return out;
1731
+ }
1732
+ if (typeof value === "object") return [value];
1733
+ return [];
1734
+ }
1735
+ function itemText(item) {
1736
+ return item["content"] || item["text"] || stringify(item);
1737
+ }
1738
+ function buildRecentMessages(turns) {
1739
+ return (_nullishCoalesce(turns, () => ( []))).map((t, idx) => ({
1740
+ role: t.role || "user",
1741
+ content: _nullishCoalesce(t.content, () => ( "")),
1742
+ timestamp: t.timestamp,
1743
+ message_id: `local-${idx}`
1744
+ }));
1745
+ }
1746
+ function compactedMessageCount(_entry) {
1747
+ return 0;
1748
+ }
1749
+ function formatStructured(entry, recent) {
1750
+ const parts = [];
1751
+ if (entry.summary) {
1752
+ parts.push("## Summary");
1753
+ parts.push(entry.summary.trim());
1754
+ }
1755
+ if (entry.factualParagraph) {
1756
+ parts.push("## Facts");
1757
+ parts.push(entry.factualParagraph.trim());
1758
+ }
1759
+ if (entry.conversationalParagraph) {
1760
+ parts.push("## Conversation");
1761
+ parts.push(entry.conversationalParagraph.trim());
1762
+ }
1763
+ const cs = _nullishCoalesce(entry.currentState, () => ( {}));
1764
+ if (Object.keys(cs).length > 0) {
1765
+ parts.push("## Current State");
1766
+ for (const [k, v] of Object.entries(cs)) parts.push(`- ${k}: ${stringify(v)}`);
1767
+ }
1768
+ const ke = _nullishCoalesce(entry.keyExtractions, () => ( {}));
1769
+ if (Object.keys(ke).length > 0) {
1770
+ parts.push("## Key Extractions");
1771
+ for (const [cat, items] of Object.entries(ke)) {
1772
+ if (!items) continue;
1773
+ parts.push(`### ${cat}`);
1774
+ for (const it of asListOfObjects(items)) parts.push(`- ${itemText(it)}`);
1775
+ }
1776
+ }
1777
+ if (recent.length > 0) {
1778
+ parts.push(`## Recent Turns (${recent.length})`);
1779
+ for (const m of recent) parts.push(`**${m.role}**: ${m.content}`);
1780
+ }
1781
+ return parts.filter(Boolean).join("\n\n").trim();
1782
+ }
1783
+ function formatNarrative(entry, recent) {
1784
+ const parts = [];
1785
+ if (entry.summary) parts.push(entry.summary.trim());
1786
+ if (entry.conversationalParagraph) parts.push(entry.conversationalParagraph.trim());
1787
+ else if (entry.factualParagraph) parts.push(entry.factualParagraph.trim());
1788
+ if (recent.length > 0) {
1789
+ const tail = recent.map((m) => `${m.role}: ${m.content}`).join("\n");
1790
+ parts.push(`Recent exchanges:
1791
+ ${tail}`);
1792
+ }
1793
+ return parts.filter(Boolean).join("\n\n").trim();
1794
+ }
1795
+ function formatBullets(entry, recent) {
1796
+ const parts = [];
1797
+ if (entry.summary) parts.push(`- Summary: ${entry.summary.trim()}`);
1798
+ for (const [k, v] of Object.entries(_nullishCoalesce(entry.currentState, () => ( {})))) {
1799
+ parts.push(`- ${k}: ${stringify(v)}`);
1800
+ }
1801
+ for (const [cat, items] of Object.entries(_nullishCoalesce(entry.keyExtractions, () => ( {})))) {
1802
+ for (const it of asListOfObjects(items)) parts.push(`- (${cat}) ${itemText(it)}`);
1803
+ }
1804
+ for (const m of recent) parts.push(`- ${m.role}: ${m.content}`);
1805
+ return parts.join("\n").trim();
1806
+ }
1807
+ function formatBlock(entry, recent, style) {
1808
+ if (style === "narrative") return formatNarrative(entry, recent);
1809
+ if (style === "bullet_points") return formatBullets(entry, recent);
1810
+ return formatStructured(entry, recent);
1811
+ }
1812
+ function renderForPrompt(entry, style = DEFAULT_STYLE, now = Date.now) {
1813
+ const chosen = SUPPORTED_STYLES.includes(style) ? style : DEFAULT_STYLE;
1814
+ const recent = buildRecentMessages(entry.recentTurns);
1815
+ const formatted = formatBlock(entry, recent, chosen);
1816
+ const available = entry.compactionId !== null || entry.recentTurns.length > 0;
1817
+ let compactionAge = null;
1818
+ if (entry.compactedAt !== null) {
1819
+ const parsed = Date.parse(entry.compactedAt);
1820
+ if (!Number.isNaN(parsed)) compactionAge = Math.floor((now() - parsed) / 1e3);
1821
+ }
1822
+ const compacted = compactedMessageCount(entry);
1823
+ return {
1824
+ formatted_context: available ? formatted : null,
1825
+ available,
1826
+ is_stale: false,
1827
+ compression_ratio: null,
1828
+ validation_score: null,
1829
+ compaction_age_seconds: compactionAge,
1830
+ quality_warning: false,
1831
+ recent_messages: recent,
1832
+ recent_message_count: recent.length,
1833
+ compacted_message_count: compacted,
1834
+ total_message_count: recent.length + compacted
1835
+ };
1836
+ }
1837
+ function renderCompacted(entry, format = "structured", correlationId = "") {
1838
+ if (entry.compactionId === null) return null;
1839
+ const formatted = formatBlock(
1840
+ entry,
1841
+ buildRecentMessages(entry.recentTurns),
1842
+ format === "narrative" ? "narrative" : "structured"
1843
+ );
1844
+ return {
1845
+ compacted_context: formatted,
1846
+ compaction_id: entry.compactionId,
1847
+ compacted_at: entry.compactedAt,
1848
+ conversation_id: entry.conversationId,
1849
+ current_state: entry.currentState,
1850
+ correlation_id: correlationId,
1851
+ original_token_count: null,
1852
+ compacted_token_count: null,
1853
+ compression_ratio: null,
1854
+ level_applied: null,
1855
+ validation_score: null,
1856
+ validation_passed: null,
1857
+ quality_warning: false
1858
+ };
1859
+ }
1860
+
1686
1861
  // src/context/user-summary.ts
1687
1862
  var USER_SUMMARY_INTERVAL = 5;
1688
1863
  var MAX_SUMMARY_ITEMS = 3;
@@ -1813,7 +1988,7 @@ function tryServeFromCache(cache, scope, params, maxItems) {
1813
1988
  });
1814
1989
  if (hit === null) return { response: null, hit: null };
1815
1990
  return { response: buildAnticipationResponse(hit), hit };
1816
- } catch (e6) {
1991
+ } catch (e7) {
1817
1992
  return { response: null, hit: null };
1818
1993
  }
1819
1994
  }
@@ -2271,6 +2446,9 @@ var SynapClient = class {
2271
2446
  #registryKeys;
2272
2447
  #compactionSubscribers;
2273
2448
  #shortTerm;
2449
+ #stVerbatimOverlay;
2450
+ #logger;
2451
+ #stAuthoritative;
2274
2452
  #turns;
2275
2453
  #fetchHooks;
2276
2454
  constructor(options = {}) {
@@ -2341,6 +2519,9 @@ var SynapClient = class {
2341
2519
  this.client = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "client", o2, this.#fetchHooks) } };
2342
2520
  this.#compactionSubscribers = new CompactionSubscribers();
2343
2521
  this.#shortTerm = new ShortTermStore();
2522
+ this.#stVerbatimOverlay = _nullishCoalesce(options.st_verbatim_overlay, () => ( options.stVerbatimOverlay));
2523
+ this.#logger = options.logger;
2524
+ this.#stAuthoritative = _nullishCoalesce(options.sdk_st_authoritative, () => ( options.sdkStAuthoritative));
2344
2525
  this.#turns = new TurnCounter();
2345
2526
  this.conversation = createConversationNamespace(
2346
2527
  this.#transport,
@@ -2361,8 +2542,12 @@ var SynapClient = class {
2361
2542
  this.#shortTerm.appendTurn(conversationId, role, content, timestamp);
2362
2543
  },
2363
2544
  (response, conversationId, userId) => {
2364
- overlayLocalRecentTurns(response, this.#shortTerm, conversationId);
2545
+ overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
2365
2546
  this.#injectUserSummary(response, conversationId, userId);
2547
+ },
2548
+ {
2549
+ prompt: (conversationId, style) => this.#localPrompt(conversationId, style),
2550
+ compacted: (conversationId, format) => this.#localCompacted(conversationId, format)
2366
2551
  }
2367
2552
  );
2368
2553
  this.memories = createMemoriesInterface(this.#transport);
@@ -2495,7 +2680,7 @@ var SynapClient = class {
2495
2680
  this.#registryKeys.push(resolvedInstanceId);
2496
2681
  }
2497
2682
  }
2498
- } catch (e7) {
2683
+ } catch (e8) {
2499
2684
  }
2500
2685
  }
2501
2686
  /**
@@ -2521,11 +2706,12 @@ var SynapClient = class {
2521
2706
  * initialization: the transport and cache are already built by then and a
2522
2707
  * half-applied config is worse than a rejected one.
2523
2708
  *
2524
- * The Python-only keys (`storage_path`, `cache_backend`,
2525
- * `session_timeout_minutes`, `logger`) are accepted and ignored rather than
2526
- * rejected, because this SDK has no SQLite cache backend and no global
2527
- * logger to swap. Ignoring them keeps a shared config object portable
2528
- * between the two SDKs.
2709
+ * `logger` is honoured here as well as on the constructor. The remaining
2710
+ * Python-only keys (`storage_path`, `cache_backend`,
2711
+ * `session_timeout_minutes`, `log_level`) are accepted and ignored rather
2712
+ * than rejected, because this SDK has no SQLite cache backend and no global
2713
+ * logging framework whose level there would be to set. Ignoring them keeps
2714
+ * a shared config object portable between the two SDKs.
2529
2715
  */
2530
2716
  configure(options = {}) {
2531
2717
  if (this.#initialized) {
@@ -2535,6 +2721,7 @@ var SynapClient = class {
2535
2721
  if (options.retryPolicy !== void 0 || options.retry_policy !== void 0) {
2536
2722
  this.#transport.setRetryPolicy(_nullishCoalesce(_nullishCoalesce(options.retryPolicy, () => ( options.retry_policy)), () => ( null)));
2537
2723
  }
2724
+ if (options.logger !== void 0) this.#logger = options.logger;
2538
2725
  }
2539
2726
  /**
2540
2727
  * Fetch and merge context across every scope with an identifier, in one call.
@@ -2632,14 +2819,14 @@ var SynapClient = class {
2632
2819
  }
2633
2820
  const merged = mergeScopeResults(successful);
2634
2821
  for (const [, response] of successful) {
2635
- overlayLocalRecentTurns(response, this.#shortTerm, conversationId);
2822
+ overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
2636
2823
  }
2637
2824
  if (includeConversationContext && conversationId !== void 0 && conversationId !== "") {
2638
2825
  try {
2639
2826
  merged.conversation_context = await this.conversation.context.get_context_for_prompt({
2640
2827
  conversation_id: conversationId
2641
2828
  });
2642
- } catch (e8) {
2829
+ } catch (e9) {
2643
2830
  }
2644
2831
  }
2645
2832
  merged.formatted_context = formatForPrompt(merged, {
@@ -2686,7 +2873,7 @@ var SynapClient = class {
2686
2873
  source_bundle_ids: served.bundleId ? [served.bundleId] : [],
2687
2874
  timestamp_ms: Date.now()
2688
2875
  });
2689
- } catch (e9) {
2876
+ } catch (e10) {
2690
2877
  }
2691
2878
  }
2692
2879
  /**
@@ -2711,7 +2898,7 @@ var SynapClient = class {
2711
2898
  }),
2712
2899
  scope
2713
2900
  });
2714
- } catch (e10) {
2901
+ } catch (e11) {
2715
2902
  }
2716
2903
  }
2717
2904
  /**
@@ -2729,8 +2916,44 @@ var SynapClient = class {
2729
2916
  if (summary === null) return;
2730
2917
  mergeUserSummary(response, summary);
2731
2918
  }
2732
- /** Python logs via `logger.warning`; here it is one overridable seam. */
2919
+ /**
2920
+ * Whether the SDK is authoritative for short-term context.
2921
+ *
2922
+ * Resolution order matches Python's `_is_st_authoritative`: the explicit
2923
+ * option first, then `SYNAP_SDK_ST_AUTHORITATIVE` (truthy strings only).
2924
+ * Defaults to false, so nothing changes for a caller who never sets it.
2925
+ */
2926
+ #isStAuthoritative() {
2927
+ if (this.#stAuthoritative === true) return true;
2928
+ const raw = (_nullishCoalesce(_chunkZKOJDUN4cjs.getEnv.call(void 0, "SYNAP_SDK_ST_AUTHORITATIVE"), () => ( ""))).trim().toLowerCase();
2929
+ return ["1", "true", "yes", "on"].includes(raw);
2930
+ }
2931
+ /**
2932
+ * Render `get_context_for_prompt` from the local store, or null to fall
2933
+ * through to the network. Warm means a compaction landed OR raw turns are
2934
+ * buffered: with neither there is nothing to render and the server's answer
2935
+ * is the only real one.
2936
+ */
2937
+ #localPrompt(conversationId, style) {
2938
+ if (!this.#isStAuthoritative()) return null;
2939
+ const entry = this.#shortTerm.get(conversationId);
2940
+ if (entry === null) return null;
2941
+ if (entry.compactionId === null && entry.recentTurns.length === 0) return null;
2942
+ return renderForPrompt(entry, style);
2943
+ }
2944
+ /** Same, for `get_compacted`. Requires an actual compaction, not just turns. */
2945
+ #localCompacted(conversationId, format) {
2946
+ if (!this.#isStAuthoritative()) return null;
2947
+ const entry = this.#shortTerm.get(conversationId);
2948
+ if (entry === null || entry.compactionId === null) return null;
2949
+ return renderCompacted(entry, format);
2950
+ }
2951
+ /** Every diagnostic the SDK emits passes through here. */
2733
2952
  #warn(message) {
2953
+ if (this.#logger !== void 0) {
2954
+ this.#logger("warn", message);
2955
+ return;
2956
+ }
2734
2957
  console.warn(`[synap] ${message}`);
2735
2958
  }
2736
2959
  /** Release timers and connections. Safe to call more than once. */
@@ -2751,7 +2974,8 @@ function pick3(o2, snake, camel) {
2751
2974
  return a !== void 0 ? a : o2[camel];
2752
2975
  }
2753
2976
  function resolveBaseUrl(options) {
2754
- if (options.baseUrl !== void 0) return options.baseUrl;
2977
+ const explicit = _nullishCoalesce(options.baseUrl, () => ( options.api_base_url));
2978
+ if (explicit !== void 0) return explicit;
2755
2979
  const fromEnv = _optionalChain([_chunkZKOJDUN4cjs.getEnv.call(void 0, "SYNAP_BASE_URL"), 'optionalAccess', _51 => _51.trim, 'call', _52 => _52()]);
2756
2980
  return fromEnv !== void 0 && fromEnv !== "" ? fromEnv : void 0;
2757
2981
  }