@maximem/synap-js-sdk 0.4.3 → 0.4.5

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.
Files changed (35) hide show
  1. package/dist/anticipation-cache-ARYEEKMU.cjs.map +1 -1
  2. package/dist/{chunk-HR5Y3KXS.cjs → chunk-2TVL5IPQ.cjs} +7 -1
  3. package/dist/chunk-2TVL5IPQ.cjs.map +1 -0
  4. package/dist/{chunk-VXRCBMCY.cjs → chunk-3JSE64GW.cjs} +1 -1
  5. package/dist/chunk-3JSE64GW.cjs.map +1 -0
  6. package/dist/{chunk-T3NZBIV2.js → chunk-OCMM2Z5P.js} +7 -1
  7. package/dist/chunk-OCMM2Z5P.js.map +1 -0
  8. package/dist/chunk-RB57I2IQ.cjs.map +1 -1
  9. package/dist/chunk-ZKOJDUN4.cjs.map +1 -1
  10. package/dist/{chunk-5SC3ELNK.js → chunk-ZU6ZC4SU.js} +1 -1
  11. package/dist/chunk-ZU6ZC4SU.js.map +1 -0
  12. package/dist/env-55ULHNJZ.cjs.map +1 -1
  13. package/dist/grpc/index.cjs +7 -7
  14. package/dist/grpc/index.cjs.map +1 -1
  15. package/dist/grpc/index.d.cts +4 -0
  16. package/dist/grpc/index.d.ts +4 -0
  17. package/dist/grpc/index.js +3 -3
  18. package/dist/index.cjs +402 -115
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +234 -27
  21. package/dist/index.d.ts +234 -27
  22. package/dist/index.js +317 -30
  23. package/dist/index.js.map +1 -1
  24. package/dist/stream-client-6V4FGKZ3.js +7 -0
  25. package/dist/stream-client-PRTOH62B.cjs +7 -0
  26. package/dist/stream-client-PRTOH62B.cjs.map +1 -0
  27. package/package.json +1 -1
  28. package/dist/chunk-5SC3ELNK.js.map +0 -1
  29. package/dist/chunk-HR5Y3KXS.cjs.map +0 -1
  30. package/dist/chunk-T3NZBIV2.js.map +0 -1
  31. package/dist/chunk-VXRCBMCY.cjs.map +0 -1
  32. package/dist/stream-client-ME5N5NK4.js +0 -7
  33. package/dist/stream-client-ZN6ADZBR.cjs +0 -7
  34. package/dist/stream-client-ZN6ADZBR.cjs.map +0 -1
  35. /package/dist/{stream-client-ME5N5NK4.js.map → stream-client-6V4FGKZ3.js.map} +0 -0
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  TranscriptConflictError,
23
23
  TransientError,
24
24
  isSynapError
25
- } from "./chunk-T3NZBIV2.js";
25
+ } from "./chunk-OCMM2Z5P.js";
26
26
  import {
27
27
  ANY_SCOPE,
28
28
  AnticipationCache,
@@ -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 = class {
217
222
  baseUrl;
218
223
  // Mutable so `configure()` can adjust them before the first request, and so
@@ -229,7 +234,7 @@ var HttpTransport = class {
229
234
  this.#credentials = options.credentials;
230
235
  this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
231
236
  this.timeouts = { ...DEFAULT_TIMEOUTS, ...options.timeouts };
232
- this.retryPolicy = { ...DEFAULT_RETRY_POLICY, ...options.retryPolicy };
237
+ this.retryPolicy = options.retryPolicy === null ? { ...DEFAULT_RETRY_POLICY, maxAttempts: 1 } : { ...DEFAULT_RETRY_POLICY, ...options.retryPolicy };
233
238
  this.userAgent = options.userAgent ?? "@maximem/synap-js-sdk";
234
239
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
235
240
  this.keepAliveOptions = options.keepAlive ?? {};
@@ -285,9 +290,16 @@ var HttpTransport = class {
285
290
  setTimeouts(timeouts) {
286
291
  this.timeouts = { ...this.timeouts, ...timeouts };
287
292
  }
288
- /** `null` restores the default policy, matching Python's `retry_policy=None`. */
293
+ /**
294
+ * `null` DISABLES retries, matching Python.
295
+ *
296
+ * Python's `SDKConfig(retry_policy=None)` leaves `self.retry_policy` unset and
297
+ * `max_attempts` falls to 1 (http_client.py:180). We restored the default
298
+ * policy instead, so a caller following the "Disabling Retries" documentation
299
+ * got three attempts and no way to turn them off.
300
+ */
289
301
  setRetryPolicy(policy) {
290
- this.retryPolicy = policy === null ? { ...DEFAULT_RETRY_POLICY } : { ...this.retryPolicy, ...policy };
302
+ this.retryPolicy = policy === null ? { ...DEFAULT_RETRY_POLICY, maxAttempts: 1 } : { ...this.retryPolicy, ...policy };
291
303
  }
292
304
  close() {
293
305
  this.closed = true;
@@ -435,7 +447,9 @@ async function toHttpError(response, correlationId) {
435
447
  return new InsufficientCreditsError(message, {
436
448
  ...opts,
437
449
  balanceCredits: numberOrNull(parsed["balance_credits"]),
438
- requiredCredits: numberOrNull(parsed["required_credits"])
450
+ requiredCredits: numberOrNull(parsed["minimum_required_credits"]) ?? numberOrNull(parsed["required_credits"]),
451
+ recoveryUrl: stringOrNull(parsed["recovery_url"]),
452
+ redeemUrl: stringOrNull(parsed["redeem_url"])
439
453
  });
440
454
  case 409:
441
455
  if (detailObject !== null && detailObject["code"] === "transcript_conflict") {
@@ -462,6 +476,9 @@ async function toHttpError(response, correlationId) {
462
476
  function numberOrNull(v) {
463
477
  return typeof v === "number" && Number.isFinite(v) ? v : null;
464
478
  }
479
+ function stringOrNull(v) {
480
+ return typeof v === "string" && v !== "" ? v : null;
481
+ }
465
482
  function withTimeout(ms, external) {
466
483
  const controller = new AbortController();
467
484
  const onExternalAbort = () => controller.abort(external?.reason);
@@ -536,9 +553,32 @@ function buildFetchRequest(scope, options) {
536
553
  }
537
554
  switch (scope) {
538
555
  case "user":
539
- if (!userId) throw new InvalidInputError("user_id is required");
540
- body["user_id"] = userId;
541
- if (customerId !== void 0) body["customer_id"] = customerId;
556
+ {
557
+ if (!userId) throw new InvalidInputError("user_id is required");
558
+ body["user_id"] = userId;
559
+ const scopePath = opt(options, "scope_path", "scopePath");
560
+ if (scopePath !== void 0) body["scope"] = scopePath;
561
+ const contextMode = opt(options, "context_mode", "contextMode") ?? "in-conversation";
562
+ if (!VALID_CONTEXT_MODES.includes(contextMode)) {
563
+ throw new InvalidInputError(
564
+ `Invalid context_mode '${contextMode}'. Must be one of: ${VALID_CONTEXT_MODES.join(", ")}`
565
+ );
566
+ }
567
+ const lastN = opt(options, "last_n_conversations", "lastNConversations") ?? 1;
568
+ if (!Number.isInteger(lastN)) {
569
+ throw new InvalidInputError("last_n_conversations must be an integer");
570
+ }
571
+ if (lastN < 0 || lastN > 20) {
572
+ throw new InvalidInputError("last_n_conversations must be between 0 and 20");
573
+ }
574
+ if (contextMode === "conversation-summary") {
575
+ body["context_mode"] = contextMode;
576
+ body["include_profile"] = opt(options, "include_profile", "includeProfile") ?? true;
577
+ body["last_n_conversations"] = lastN;
578
+ }
579
+ if (customerId !== void 0) body["customer_id"] = customerId;
580
+ break;
581
+ }
542
582
  break;
543
583
  case "customer":
544
584
  if (!customerId) throw new InvalidInputError("customer_id is required");
@@ -554,6 +594,7 @@ function buildFetchRequest(scope, options) {
554
594
  }
555
595
  return { endpoint: ENDPOINT_BY_SCOPE[scope], body };
556
596
  }
597
+ var VALID_CONTEXT_MODES = ["in-conversation", "conversation-summary"];
557
598
  async function fetchContext(transport, scope, options = {}, hooks = {}) {
558
599
  const startedAt = Date.now();
559
600
  transport.checkCustomerId(
@@ -959,6 +1000,15 @@ function validateInstanceId(instanceId) {
959
1000
  }
960
1001
 
961
1002
  // src/conversation/interface.ts
1003
+ var COMPACTION_LEVELS = [
1004
+ "low",
1005
+ "medium",
1006
+ "high",
1007
+ "adaptive",
1008
+ "aggressive",
1009
+ "balanced",
1010
+ "conservative"
1011
+ ];
962
1012
  function either2(o2, snake, camel) {
963
1013
  const a = o2[snake];
964
1014
  if (a !== void 0) return a;
@@ -1076,7 +1126,7 @@ var CompactionSubscribers = class {
1076
1126
  function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
1077
1127
  }, onTurn = () => {
1078
1128
  }, onFetched = () => {
1079
- }) {
1129
+ }, localShortTerm = { prompt: () => null, compacted: () => null }) {
1080
1130
  const convId = (v) => {
1081
1131
  const id = typeof v === "string" ? v : v.conversation_id ?? v.conversationId;
1082
1132
  if (!id) throw new InvalidInputError("conversation_id is required");
@@ -1157,6 +1207,8 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1157
1207
  },
1158
1208
  get_context_for_prompt: async (options = {}) => {
1159
1209
  const id = convId(options);
1210
+ const local = localShortTerm.prompt(id, options.style ?? "structured");
1211
+ if (local !== null) return local;
1160
1212
  const result = await transport.request("context_for_prompt", {
1161
1213
  pathParams: { conversation_id: id },
1162
1214
  query: { style: options.style ?? "structured" }
@@ -1168,6 +1220,10 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1168
1220
  },
1169
1221
  get_compacted: async (options) => {
1170
1222
  const id = convId(options);
1223
+ if (options.version === void 0) {
1224
+ const local = localShortTerm.compacted(id, options.format ?? "structured");
1225
+ if (local !== null) return local;
1226
+ }
1171
1227
  const query = { format: options.format ?? "structured" };
1172
1228
  if (options.version !== void 0) query["version"] = options.version;
1173
1229
  try {
@@ -1229,15 +1285,26 @@ function createUserNamespace(transport, hooks = {}) {
1229
1285
  }
1230
1286
 
1231
1287
  // src/credits/interface.ts
1288
+ function iso2(v) {
1289
+ return v instanceof Date ? v.toISOString() : v;
1290
+ }
1232
1291
  function createCreditsNamespace(transport) {
1233
1292
  return {
1234
1293
  async get_balance() {
1235
1294
  return transport.request("credits_balance");
1236
1295
  },
1237
1296
  async get_ledger(options = {}) {
1238
- return transport.request("credits_ledger", {
1239
- query: { limit: options.limit ?? 50, offset: options.offset ?? 0 }
1240
- });
1297
+ const query = {
1298
+ limit: options.limit ?? 100,
1299
+ offset: options.offset ?? 0
1300
+ };
1301
+ const entryType = options.entry_type ?? options.entryType;
1302
+ if (entryType !== void 0) query["entry_type"] = entryType;
1303
+ const from = options.from_time ?? options.fromTime;
1304
+ if (from !== void 0) query["from"] = iso2(from);
1305
+ const to = options.to_time ?? options.toTime;
1306
+ if (to !== void 0) query["to"] = iso2(to);
1307
+ return transport.request("credits_ledger", { query });
1241
1308
  },
1242
1309
  async estimate(options) {
1243
1310
  return transport.request("credits_estimate", {
@@ -1287,6 +1354,18 @@ function envTls() {
1287
1354
  if (raw === void 0 || raw === "") return void 0;
1288
1355
  return !["0", "false", "no", "off"].includes(raw.trim().toLowerCase());
1289
1356
  }
1357
+ function thinkingMetadata(options) {
1358
+ const md = { ...options.metadata ?? {} };
1359
+ const step = 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 = options.thought_type ?? options.thoughtType;
1364
+ if (kind && md["thought_type"] === void 0) {
1365
+ md["thought_type"] = kind;
1366
+ }
1367
+ return md;
1368
+ }
1290
1369
  function createInstanceNamespace(credentials, cache, onTurn) {
1291
1370
  let client = null;
1292
1371
  let dispatchCompaction = null;
@@ -1317,7 +1396,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1317
1396
  if (client !== null) {
1318
1397
  throw new ListeningAlreadyActiveError("Listening is already active on this instance");
1319
1398
  }
1320
- const { GrpcStreamClient } = await import("./stream-client-ME5N5NK4.js");
1399
+ const { GrpcStreamClient } = await import("./stream-client-6V4FGKZ3.js");
1321
1400
  const onContext = options.on_context ?? options.onContext;
1322
1401
  const onDisconnect = options.on_disconnect ?? options.onDisconnect;
1323
1402
  const onReconnect = options.on_reconnect ?? options.onReconnect;
@@ -1377,6 +1456,12 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1377
1456
  session_id: options.session_id ?? options.sessionId ?? "",
1378
1457
  metadata: options.metadata ?? {},
1379
1458
  timestamp_ms: Date.now(),
1459
+ tool_name: options.tool_name ?? options.toolName ?? "",
1460
+ // Python serialises tool_args with json.dumps into tool_args_json.
1461
+ tool_args_json: (() => {
1462
+ const a = options.tool_args ?? options.toolArgs;
1463
+ return a === void 0 ? "" : JSON.stringify(a);
1464
+ })(),
1380
1465
  search_queries: options.search_queries ?? options.searchQueries ?? [],
1381
1466
  context_types: options.context_types ?? options.contextTypes ?? []
1382
1467
  });
@@ -1392,7 +1477,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1392
1477
  user_id: options.user_id ?? options.userId ?? "",
1393
1478
  customer_id: options.customer_id ?? options.customerId ?? "",
1394
1479
  session_id: options.session_id ?? options.sessionId ?? "",
1395
- metadata: options.metadata ?? {},
1480
+ metadata: thinkingMetadata(options),
1396
1481
  timestamp_ms: Date.now()
1397
1482
  });
1398
1483
  },
@@ -1575,11 +1660,11 @@ var ShortTermStore = class {
1575
1660
  // src/context/overlay.ts
1576
1661
  var MAX_TURNS = 20;
1577
1662
  var TOKEN_BUDGET = 2e3;
1578
- function verbatimOverlayEnabled() {
1663
+ function verbatimOverlayEnabled(configured) {
1579
1664
  const raw = (getEnv("SYNAP_ST_VERBATIM_OVERLAY") ?? "").trim().toLowerCase();
1580
1665
  if (["0", "false", "no", "off"].includes(raw)) return false;
1581
1666
  if (["1", "true", "yes", "on"].includes(raw)) return true;
1582
- return true;
1667
+ return configured ?? true;
1583
1668
  }
1584
1669
  function estimateTokens(turn) {
1585
1670
  return Math.max(1, Math.floor(String(turn.content ?? "").length / 4));
@@ -1593,13 +1678,13 @@ function budgetRecentTurns(turns) {
1593
1678
  }
1594
1679
  return windowed;
1595
1680
  }
1596
- function overlayLocalRecentTurns(response, store, conversationId) {
1681
+ function overlayLocalRecentTurns(response, store, conversationId, configured) {
1597
1682
  if (conversationId === void 0 || conversationId === "") return;
1598
1683
  try {
1599
1684
  const entry = store.get(conversationId);
1600
1685
  if (entry === null) return;
1601
1686
  const existing = response.conversation_context ?? null;
1602
- if (existing !== null && !verbatimOverlayEnabled()) return;
1687
+ if (existing !== null && !verbatimOverlayEnabled(configured)) return;
1603
1688
  const localTurns = budgetRecentTurns(entry.recentTurns);
1604
1689
  const serverField = (name) => existing === null ? void 0 : existing[name];
1605
1690
  const orLocal = (name, local) => {
@@ -1621,6 +1706,158 @@ function overlayLocalRecentTurns(response, store, conversationId) {
1621
1706
  }
1622
1707
  }
1623
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 JSON.stringify(value) ?? "";
1718
+ } catch {
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 (turns ?? []).map((t, idx) => ({
1740
+ role: t.role || "user",
1741
+ content: 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 = 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 = 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(entry.currentState ?? {})) {
1799
+ parts.push(`- ${k}: ${stringify(v)}`);
1800
+ }
1801
+ for (const [cat, items] of Object.entries(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
+
1624
1861
  // src/context/user-summary.ts
1625
1862
  var USER_SUMMARY_INTERVAL = 5;
1626
1863
  var MAX_SUMMARY_ITEMS = 3;
@@ -2209,6 +2446,9 @@ var SynapClient = class {
2209
2446
  #registryKeys;
2210
2447
  #compactionSubscribers;
2211
2448
  #shortTerm;
2449
+ #stVerbatimOverlay;
2450
+ #logger;
2451
+ #stAuthoritative;
2212
2452
  #turns;
2213
2453
  #fetchHooks;
2214
2454
  constructor(options = {}) {
@@ -2279,6 +2519,9 @@ var SynapClient = class {
2279
2519
  this.client = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "client", o2, this.#fetchHooks) } };
2280
2520
  this.#compactionSubscribers = new CompactionSubscribers();
2281
2521
  this.#shortTerm = new ShortTermStore();
2522
+ this.#stVerbatimOverlay = options.st_verbatim_overlay ?? options.stVerbatimOverlay;
2523
+ this.#logger = options.logger;
2524
+ this.#stAuthoritative = options.sdk_st_authoritative ?? options.sdkStAuthoritative;
2282
2525
  this.#turns = new TurnCounter();
2283
2526
  this.conversation = createConversationNamespace(
2284
2527
  this.#transport,
@@ -2299,8 +2542,12 @@ var SynapClient = class {
2299
2542
  this.#shortTerm.appendTurn(conversationId, role, content, timestamp);
2300
2543
  },
2301
2544
  (response, conversationId, userId) => {
2302
- overlayLocalRecentTurns(response, this.#shortTerm, conversationId);
2545
+ overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
2303
2546
  this.#injectUserSummary(response, conversationId, userId);
2547
+ },
2548
+ {
2549
+ prompt: (conversationId, style) => this.#localPrompt(conversationId, style),
2550
+ compacted: (conversationId, format) => this.#localCompacted(conversationId, format)
2304
2551
  }
2305
2552
  );
2306
2553
  this.memories = createMemoriesInterface(this.#transport);
@@ -2422,8 +2669,8 @@ var SynapClient = class {
2422
2669
  const whoami = await this.#transport.request("whoami", {});
2423
2670
  const resolvedClientId = String(whoami["client_id"] ?? "");
2424
2671
  const resolvedInstanceId = String(whoami["instance_id"] ?? "");
2425
- const iso2 = whoami["user_context_isolation"];
2426
- this.#transport.userContextIsolation = typeof iso2 === "string" ? iso2 : void 0;
2672
+ const iso3 = whoami["user_context_isolation"];
2673
+ this.#transport.userContextIsolation = typeof iso3 === "string" ? iso3 : void 0;
2427
2674
  this.#transport.updateCredentials({
2428
2675
  ...resolvedClientId !== "" && credentials.clientId === "" ? { clientId: resolvedClientId } : {},
2429
2676
  ...resolvedInstanceId !== "" && credentials.instanceId === "" ? { instanceId: resolvedInstanceId } : {}
@@ -2459,11 +2706,12 @@ var SynapClient = class {
2459
2706
  * initialization: the transport and cache are already built by then and a
2460
2707
  * half-applied config is worse than a rejected one.
2461
2708
  *
2462
- * The Python-only keys (`storage_path`, `cache_backend`,
2463
- * `session_timeout_minutes`, `logger`) are accepted and ignored rather than
2464
- * rejected, because this SDK has no SQLite cache backend and no global
2465
- * logger to swap. Ignoring them keeps a shared config object portable
2466
- * 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.
2467
2715
  */
2468
2716
  configure(options = {}) {
2469
2717
  if (this.#initialized) {
@@ -2473,6 +2721,7 @@ var SynapClient = class {
2473
2721
  if (options.retryPolicy !== void 0 || options.retry_policy !== void 0) {
2474
2722
  this.#transport.setRetryPolicy(options.retryPolicy ?? options.retry_policy ?? null);
2475
2723
  }
2724
+ if (options.logger !== void 0) this.#logger = options.logger;
2476
2725
  }
2477
2726
  /**
2478
2727
  * Fetch and merge context across every scope with an identifier, in one call.
@@ -2570,7 +2819,7 @@ var SynapClient = class {
2570
2819
  }
2571
2820
  const merged = mergeScopeResults(successful);
2572
2821
  for (const [, response] of successful) {
2573
- overlayLocalRecentTurns(response, this.#shortTerm, conversationId);
2822
+ overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
2574
2823
  }
2575
2824
  if (includeConversationContext && conversationId !== void 0 && conversationId !== "") {
2576
2825
  try {
@@ -2667,8 +2916,44 @@ var SynapClient = class {
2667
2916
  if (summary === null) return;
2668
2917
  mergeUserSummary(response, summary);
2669
2918
  }
2670
- /** 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 = (getEnv("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. */
2671
2952
  #warn(message) {
2953
+ if (this.#logger !== void 0) {
2954
+ this.#logger("warn", message);
2955
+ return;
2956
+ }
2672
2957
  console.warn(`[synap] ${message}`);
2673
2958
  }
2674
2959
  /** Release timers and connections. Safe to call more than once. */
@@ -2689,7 +2974,8 @@ function pick3(o2, snake, camel) {
2689
2974
  return a !== void 0 ? a : o2[camel];
2690
2975
  }
2691
2976
  function resolveBaseUrl(options) {
2692
- if (options.baseUrl !== void 0) return options.baseUrl;
2977
+ const explicit = options.baseUrl ?? options.api_base_url;
2978
+ if (explicit !== void 0) return explicit;
2693
2979
  const fromEnv = getEnv("SYNAP_BASE_URL")?.trim();
2694
2980
  return fromEnv !== void 0 && fromEnv !== "" ? fromEnv : void 0;
2695
2981
  }
@@ -2712,6 +2998,7 @@ export {
2712
2998
  AnticipationCache,
2713
2999
  AuthenticationError,
2714
3000
  BM25,
3001
+ COMPACTION_LEVELS,
2715
3002
  CONTRACT,
2716
3003
  ConflictError,
2717
3004
  ConnectionError,