@maximem/synap-js-sdk 0.2.4 → 1.0.0-beta.0

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 (55) hide show
  1. package/MIGRATING.md +146 -0
  2. package/README.md +96 -83
  3. package/bin/synap-js-sdk.js +45 -122
  4. package/dist/anticipation-cache-BgkqcTPJ.d.cts +180 -0
  5. package/dist/anticipation-cache-BgkqcTPJ.d.ts +180 -0
  6. package/dist/anticipation-cache-D5YHEPV3.js +10 -0
  7. package/dist/anticipation-cache-D5YHEPV3.js.map +1 -0
  8. package/dist/anticipation-cache-JBZHTJ3O.cjs +10 -0
  9. package/dist/anticipation-cache-JBZHTJ3O.cjs.map +1 -0
  10. package/dist/chunk-5SC3ELNK.js +856 -0
  11. package/dist/chunk-5SC3ELNK.js.map +1 -0
  12. package/dist/chunk-HR5Y3KXS.cjs +172 -0
  13. package/dist/chunk-HR5Y3KXS.cjs.map +1 -0
  14. package/dist/chunk-NIDPTBNC.cjs +800 -0
  15. package/dist/chunk-NIDPTBNC.cjs.map +1 -0
  16. package/dist/chunk-QBCVMPGK.js +28 -0
  17. package/dist/chunk-QBCVMPGK.js.map +1 -0
  18. package/dist/chunk-T3NZBIV2.js +172 -0
  19. package/dist/chunk-T3NZBIV2.js.map +1 -0
  20. package/dist/chunk-TCEP5OVR.js +800 -0
  21. package/dist/chunk-TCEP5OVR.js.map +1 -0
  22. package/dist/chunk-VXRCBMCY.cjs +856 -0
  23. package/dist/chunk-VXRCBMCY.cjs.map +1 -0
  24. package/dist/chunk-ZKOJDUN4.cjs +28 -0
  25. package/dist/chunk-ZKOJDUN4.cjs.map +1 -0
  26. package/dist/env-55ULHNJZ.cjs +11 -0
  27. package/dist/env-55ULHNJZ.cjs.map +1 -0
  28. package/dist/env-QCUKKW36.js +11 -0
  29. package/dist/env-QCUKKW36.js.map +1 -0
  30. package/dist/grpc/index.cjs +87 -0
  31. package/dist/grpc/index.cjs.map +1 -0
  32. package/dist/grpc/index.d.cts +162 -0
  33. package/dist/grpc/index.d.ts +162 -0
  34. package/dist/grpc/index.js +87 -0
  35. package/dist/grpc/index.js.map +1 -0
  36. package/dist/index.cjs +2111 -0
  37. package/dist/index.cjs.map +1 -0
  38. package/dist/index.d.cts +1477 -0
  39. package/dist/index.d.ts +1477 -0
  40. package/dist/index.js +2111 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/stream-client-ME5N5NK4.js +7 -0
  43. package/dist/stream-client-ME5N5NK4.js.map +1 -0
  44. package/dist/stream-client-ZN6ADZBR.cjs +7 -0
  45. package/dist/stream-client-ZN6ADZBR.cjs.map +1 -0
  46. package/package.json +68 -19
  47. package/.env.example +0 -39
  48. package/bridge/synap_bridge.py +0 -708
  49. package/src/bridge-manager.js +0 -262
  50. package/src/errors.js +0 -151
  51. package/src/index.js +0 -13
  52. package/src/runtime.js +0 -161
  53. package/src/setup-typescript.js +0 -161
  54. package/src/synap-client.js +0 -363
  55. package/types/index.d.ts +0 -384
package/dist/index.js ADDED
@@ -0,0 +1,2111 @@
1
+ import {
2
+ AgentUnavailableError,
3
+ AuthenticationError,
4
+ ConflictError,
5
+ ConnectionError,
6
+ ContextNotFoundError,
7
+ InsufficientCreditsError,
8
+ InvalidConversationIdError,
9
+ InvalidInputError,
10
+ InvalidInstanceIdError,
11
+ ListeningAlreadyActiveError,
12
+ ListeningNotActiveError,
13
+ NetworkTimeoutError,
14
+ PermanentError,
15
+ RateLimitError,
16
+ SDKError,
17
+ ServiceUnavailableError,
18
+ SessionExpiredError,
19
+ SynapError,
20
+ SynapPermanentError,
21
+ SynapTransientError,
22
+ TranscriptConflictError,
23
+ TransientError,
24
+ isSynapError
25
+ } from "./chunk-T3NZBIV2.js";
26
+ import {
27
+ ANY_SCOPE,
28
+ AnticipationCache,
29
+ BM25,
30
+ CONTRACT,
31
+ THRESHOLDS,
32
+ isRecallQuery,
33
+ stem,
34
+ tokenize
35
+ } from "./chunk-TCEP5OVR.js";
36
+ import {
37
+ getEnv
38
+ } from "./chunk-QBCVMPGK.js";
39
+
40
+ // src/util/correlation.ts
41
+ function newCorrelationId() {
42
+ const c = globalThis.crypto;
43
+ if (typeof c?.randomUUID === "function") return c.randomUUID();
44
+ if (typeof c?.getRandomValues === "function") {
45
+ const b = c.getRandomValues(new Uint8Array(16));
46
+ b[6] = (b[6] ?? 0) & 15 | 64;
47
+ b[8] = (b[8] ?? 0) & 63 | 128;
48
+ const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
49
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
50
+ }
51
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
52
+ const r = Math.random() * 16 | 0;
53
+ return (ch === "x" ? r : r & 3 | 8).toString(16);
54
+ });
55
+ }
56
+
57
+ // src/transport/endpoints.ts
58
+ var ENDPOINTS = {
59
+ whoami: { method: "GET", path: "/api/v1/auth/whoami", idempotent: true },
60
+ memories_create: { method: "POST", path: "/api/v1/memories/create", idempotent: false },
61
+ memories_batch: { method: "POST", path: "/api/v1/memories/batch", idempotent: false },
62
+ memories_upload: { method: "POST", path: "/api/v1/memories/upload", idempotent: false },
63
+ memories_status: { method: "GET", path: "/api/v1/memories/status/{ingestion_id}", idempotent: true },
64
+ memories_get: { method: "GET", path: "/api/v1/memories/{memory_id}", idempotent: true },
65
+ memories_update: { method: "PATCH", path: "/api/v1/memories/{memory_id}", idempotent: false },
66
+ memories_delete: { method: "DELETE", path: "/api/v1/memories/{memory_id}", idempotent: true },
67
+ context_fetch_user: { method: "POST", path: "/v1/context/user/fetch", idempotent: true },
68
+ context_fetch_customer: { method: "POST", path: "/v1/context/customer/fetch", idempotent: true },
69
+ context_fetch_client: { method: "POST", path: "/v1/context/client/fetch", idempotent: true },
70
+ context_fetch_conversation: { method: "POST", path: "/v1/context/conversation/fetch", idempotent: true },
71
+ context_for_prompt: { method: "GET", path: "/v1/conversations/{conversation_id}/context-for-prompt", idempotent: true },
72
+ conversations_ingest: { method: "POST", path: "/v1/conversations/ingest", idempotent: false },
73
+ conversations_messages: { method: "POST", path: "/v1/conversations/messages", idempotent: false },
74
+ conversations_messages_batch: { method: "POST", path: "/v1/conversations/messages/batch", idempotent: false },
75
+ conversations_compact: { method: "POST", path: "/v1/conversations/compact", idempotent: false },
76
+ conversations_compacted: { method: "GET", path: "/v1/conversations/{conversation_id}/compacted", idempotent: true },
77
+ conversations_compaction_status: { method: "GET", path: "/v1/conversations/{conversation_id}/compaction/status", idempotent: true },
78
+ user_profile: { method: "GET", path: "/v1/users/{user_id}/profile", idempotent: true },
79
+ credits_balance: { method: "GET", path: "/v1/credits/balance", idempotent: true },
80
+ credits_ledger: { method: "GET", path: "/v1/credits/ledger", idempotent: true },
81
+ credits_estimate: { method: "POST", path: "/v1/credits/estimate", idempotent: true },
82
+ credits_redeem: { method: "POST", path: "/v1/credits/redeem", idempotent: false },
83
+ telemetry_batch: { method: "POST", path: "/v1/telemetry/batch", idempotent: false }
84
+ };
85
+ function resolvePath(name, params = {}) {
86
+ return ENDPOINTS[name].path.replace(/\{(\w+)\}/g, (_m, key) => {
87
+ const value = params[key];
88
+ if (value === void 0 || value === null || value === "") {
89
+ throw new Error(
90
+ `Endpoint ${name} requires path parameter '${key}'.`
91
+ );
92
+ }
93
+ return encodeURIComponent(String(value));
94
+ });
95
+ }
96
+
97
+ // src/transport/retry.ts
98
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
99
+ "GET",
100
+ "HEAD",
101
+ "OPTIONS",
102
+ "PUT",
103
+ "DELETE"
104
+ ]);
105
+ var DEFAULT_RETRY_POLICY = {
106
+ maxAttempts: 3,
107
+ backoffBase: 1,
108
+ backoffMax: 10,
109
+ backoffJitter: true
110
+ };
111
+ function shouldRetry(input) {
112
+ const { error, attempt, policy, method, outcomeUnknown } = input;
113
+ if (attempt >= policy.maxAttempts) {
114
+ return { retry: false, delaySeconds: 0, reason: "max_attempts_exhausted" };
115
+ }
116
+ if (!isSynapError(error) || !error.transient) {
117
+ return { retry: false, delaySeconds: 0, reason: "not_transient" };
118
+ }
119
+ const idempotent = input.idempotent ?? IDEMPOTENT_METHODS.has(method.toUpperCase());
120
+ if (outcomeUnknown && !idempotent) {
121
+ return {
122
+ retry: false,
123
+ delaySeconds: 0,
124
+ reason: "non_idempotent_outcome_unknown"
125
+ };
126
+ }
127
+ return {
128
+ retry: true,
129
+ delaySeconds: backoffDelay(error, attempt, policy),
130
+ reason: "transient_retryable"
131
+ };
132
+ }
133
+ function backoffDelay(error, attempt, policy) {
134
+ if (error instanceof RateLimitError && error.retryAfterSeconds != null) {
135
+ return Math.min(error.retryAfterSeconds, policy.backoffMax);
136
+ }
137
+ const exponential = policy.backoffBase * 2 ** (attempt - 1);
138
+ const capped = Math.min(exponential, policy.backoffMax);
139
+ if (!policy.backoffJitter) return capped;
140
+ return Math.random() * capped;
141
+ }
142
+
143
+ // src/transport/keepalive.ts
144
+ var KEEPALIVE_EXPIRY_MS = 3e5;
145
+ var HEARTBEAT_INTERVAL_MS = 24e4;
146
+ var MAX_CONNECTIONS = 20;
147
+ var cached;
148
+ function supportsDispatcher() {
149
+ return typeof process !== "undefined" && process.versions?.node !== void 0 && // Edge builds of Next expose a partial `process` shim, but no real Node.
150
+ typeof globalThis.EdgeRuntime === "undefined";
151
+ }
152
+ async function getKeepAliveDispatcher(options = {}) {
153
+ if (cached !== void 0) return cached;
154
+ if (!supportsDispatcher()) {
155
+ cached = null;
156
+ return cached;
157
+ }
158
+ try {
159
+ const undici = await import(
160
+ /* @vite-ignore */
161
+ "undici"
162
+ );
163
+ cached = new undici.Agent({
164
+ keepAliveTimeout: options.keepAliveExpiryMs ?? KEEPALIVE_EXPIRY_MS,
165
+ // undici will not honour a keepAliveTimeout above this ceiling.
166
+ keepAliveMaxTimeout: options.keepAliveExpiryMs ?? KEEPALIVE_EXPIRY_MS,
167
+ connections: options.maxConnections ?? MAX_CONNECTIONS
168
+ });
169
+ } catch {
170
+ cached = null;
171
+ }
172
+ return cached;
173
+ }
174
+ var Heartbeat = class {
175
+ constructor(ping, intervalMs = HEARTBEAT_INTERVAL_MS) {
176
+ this.ping = ping;
177
+ this.intervalMs = intervalMs;
178
+ }
179
+ ping;
180
+ intervalMs;
181
+ timer = null;
182
+ start() {
183
+ if (this.timer !== null) return;
184
+ if (this.intervalMs <= 0) return;
185
+ if (typeof setInterval !== "function") return;
186
+ this.timer = setInterval(() => {
187
+ void Promise.resolve(this.ping()).catch(() => {
188
+ });
189
+ }, this.intervalMs);
190
+ this.timer.unref?.();
191
+ }
192
+ stop() {
193
+ if (this.timer !== null) {
194
+ clearInterval(this.timer);
195
+ this.timer = null;
196
+ }
197
+ }
198
+ get running() {
199
+ return this.timer !== null;
200
+ }
201
+ };
202
+
203
+ // src/transport/http.ts
204
+ var DEFAULT_BASE_URL = "https://synap-cloud-prod.maximem.ai";
205
+ var DEFAULT_TIMEOUTS = { connect: 5, read: 30, write: 10 };
206
+ var HttpTransport = class {
207
+ baseUrl;
208
+ // Mutable so `configure()` can adjust them before the first request, and so
209
+ // `initialize()` can write back the identity resolved from whoami.
210
+ timeouts;
211
+ retryPolicy;
212
+ #credentials;
213
+ userAgent;
214
+ fetchImpl;
215
+ keepAliveOptions;
216
+ heartbeat;
217
+ closed = false;
218
+ constructor(options) {
219
+ this.#credentials = options.credentials;
220
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
221
+ this.timeouts = { ...DEFAULT_TIMEOUTS, ...options.timeouts };
222
+ this.retryPolicy = { ...DEFAULT_RETRY_POLICY, ...options.retryPolicy };
223
+ this.userAgent = options.userAgent ?? "@maximem/synap-js-sdk";
224
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
225
+ this.keepAliveOptions = options.keepAlive ?? {};
226
+ this.heartbeat = new Heartbeat(
227
+ () => this.pingHealth(),
228
+ options.keepAlive?.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS
229
+ );
230
+ }
231
+ /** Start keeping the connection warm. Opt-in: it is pointless in serverless. */
232
+ startHeartbeat() {
233
+ if (!this.closed) this.heartbeat.start();
234
+ }
235
+ /**
236
+ * Release timers. Safe to call more than once.
237
+ *
238
+ * This is the supported shutdown path, deliberately explicit rather than
239
+ * hooked to `process.on('exit')`: those handlers leak a listener per client,
240
+ * never fire in Lambda, and `process` does not exist in Workers (gotcha G-I).
241
+ */
242
+ /**
243
+ * A COPY of the current credentials.
244
+ *
245
+ * A copy, not the live object: handing out the internal reference is how
246
+ * `client.transport.credentials.apiKey` became readable and mutable from
247
+ * outside in the first place.
248
+ */
249
+ currentCredentials() {
250
+ return { ...this.#credentials };
251
+ }
252
+ /** Write back identity resolved from `GET /api/v1/auth/whoami`. */
253
+ updateCredentials(patch) {
254
+ this.#credentials = { ...this.#credentials, ...patch };
255
+ }
256
+ setTimeouts(timeouts) {
257
+ this.timeouts = { ...this.timeouts, ...timeouts };
258
+ }
259
+ /** `null` restores the default policy, matching Python's `retry_policy=None`. */
260
+ setRetryPolicy(policy) {
261
+ this.retryPolicy = policy === null ? { ...DEFAULT_RETRY_POLICY } : { ...this.retryPolicy, ...policy };
262
+ }
263
+ close() {
264
+ this.closed = true;
265
+ this.heartbeat.stop();
266
+ }
267
+ async request(name, options = {}) {
268
+ const spec = ENDPOINTS[name];
269
+ const correlationId = options.correlationId ?? newCorrelationId();
270
+ const idempotent = options.idempotent ?? spec.idempotent;
271
+ let attempt = 0;
272
+ let last = null;
273
+ while (attempt < this.retryPolicy.maxAttempts) {
274
+ attempt += 1;
275
+ try {
276
+ return await this.attempt(name, options, correlationId);
277
+ } catch (error) {
278
+ last = { error, outcomeUnknown: isOutcomeUnknown(error) };
279
+ const decision = shouldRetry({
280
+ error,
281
+ attempt,
282
+ policy: this.retryPolicy,
283
+ method: spec.method,
284
+ outcomeUnknown: last.outcomeUnknown,
285
+ idempotent
286
+ });
287
+ if (!decision.retry) throw error;
288
+ await sleep(decision.delaySeconds * 1e3, options.signal);
289
+ }
290
+ }
291
+ throw last?.error ?? new SynapError("Request failed with no recorded error");
292
+ }
293
+ async attempt(name, options, correlationId) {
294
+ const spec = ENDPOINTS[name];
295
+ const url = this.buildUrl(resolvePath(name, options.pathParams), options.query);
296
+ const dispatcher = await getKeepAliveDispatcher(this.keepAliveOptions);
297
+ const signal = withTimeout(this.timeouts.read * 1e3, options.signal);
298
+ const init = {
299
+ method: spec.method,
300
+ headers: this.buildHeaders(correlationId, { json: options.rawBody !== true }),
301
+ signal: signal.signal
302
+ };
303
+ if (options.body !== void 0 && spec.method !== "GET") {
304
+ init.body = options.rawBody === true ? options.body : JSON.stringify(options.body);
305
+ }
306
+ if (dispatcher) init["dispatcher"] = dispatcher;
307
+ let response;
308
+ try {
309
+ response = await this.fetchImpl(url, init);
310
+ } catch (error) {
311
+ throw toTransportError(error, correlationId);
312
+ } finally {
313
+ signal.dispose();
314
+ }
315
+ if (!response.ok) {
316
+ throw await toHttpError(response, correlationId);
317
+ }
318
+ if (response.status === 204) return void 0;
319
+ const text = await response.text();
320
+ if (!text) return void 0;
321
+ try {
322
+ return JSON.parse(text);
323
+ } catch {
324
+ throw new SynapError(`Malformed JSON in response from ${url}`, { correlationId });
325
+ }
326
+ }
327
+ buildUrl(path, query) {
328
+ const url = new URL(this.baseUrl + path);
329
+ for (const [k, v] of Object.entries(query ?? {})) {
330
+ if (v !== void 0) url.searchParams.set(k, String(v));
331
+ }
332
+ return url.toString();
333
+ }
334
+ buildHeaders(correlationId, options = {}) {
335
+ return {
336
+ Authorization: `Bearer ${this.#credentials.apiKey}`,
337
+ "X-Client-ID": this.#credentials.clientId,
338
+ "X-Instance-ID": this.#credentials.instanceId,
339
+ "X-Correlation-ID": correlationId,
340
+ ...options.json === false ? {} : { "Content-Type": "application/json" },
341
+ "User-Agent": this.userAgent
342
+ };
343
+ }
344
+ /** Unauthenticated and unmetered, exactly like the Python heartbeat. */
345
+ async pingHealth() {
346
+ const signal = withTimeout(1e4);
347
+ try {
348
+ const dispatcher = await getKeepAliveDispatcher(this.keepAliveOptions);
349
+ const init = { signal: signal.signal };
350
+ if (dispatcher) init["dispatcher"] = dispatcher;
351
+ await this.fetchImpl(`${this.baseUrl}/health`, init);
352
+ } finally {
353
+ signal.dispose();
354
+ }
355
+ }
356
+ };
357
+ function isOutcomeUnknown(error) {
358
+ if (error instanceof RateLimitError) return false;
359
+ if (error instanceof ServiceUnavailableError) return false;
360
+ if (error instanceof AuthenticationError) return false;
361
+ if (error instanceof InvalidInputError) return false;
362
+ if (error instanceof ConflictError) return false;
363
+ if (error instanceof InsufficientCreditsError) return false;
364
+ if (error instanceof ContextNotFoundError) return false;
365
+ if (error instanceof NetworkTimeoutError) return true;
366
+ if (isConnectionRefusal(error)) return false;
367
+ return true;
368
+ }
369
+ function isConnectionRefusal(error) {
370
+ const code = error?.cause?.code ?? error?.code;
371
+ return code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "EAI_AGAIN" || code === "ECONNRESET" || code === "EHOSTUNREACH";
372
+ }
373
+ function toTransportError(error, correlationId) {
374
+ if (error instanceof SynapError) return error;
375
+ const name = error?.name;
376
+ if (name === "AbortError" || name === "TimeoutError") {
377
+ return new NetworkTimeoutError("Request timed out", { correlationId, cause: error });
378
+ }
379
+ return new ServiceUnavailableError(
380
+ `Network request failed: ${error?.message ?? String(error)}`,
381
+ { correlationId, cause: error }
382
+ );
383
+ }
384
+ async function toHttpError(response, correlationId) {
385
+ const body = await response.text().catch(() => "");
386
+ let parsed = {};
387
+ try {
388
+ parsed = JSON.parse(body);
389
+ } catch {
390
+ }
391
+ const rawDetail = parsed["detail"] ?? parsed["message"] ?? body ?? "";
392
+ const detailObject = typeof rawDetail === "object" && rawDetail !== null ? rawDetail : null;
393
+ const detail = detailObject !== null ? String(detailObject["message"] ?? JSON.stringify(detailObject)) : String(rawDetail);
394
+ const message = `HTTP ${response.status}: ${detail || response.statusText}`;
395
+ const opts = { correlationId };
396
+ switch (response.status) {
397
+ case 400:
398
+ case 422:
399
+ return new InvalidInputError(message, opts);
400
+ case 401:
401
+ case 403:
402
+ return new AuthenticationError(message, opts);
403
+ case 404:
404
+ return new ContextNotFoundError(message, opts);
405
+ case 402:
406
+ return new InsufficientCreditsError(message, {
407
+ ...opts,
408
+ balanceCredits: numberOrNull(parsed["balance_credits"]),
409
+ requiredCredits: numberOrNull(parsed["required_credits"])
410
+ });
411
+ case 409:
412
+ if (detailObject !== null && detailObject["code"] === "transcript_conflict") {
413
+ return new TranscriptConflictError(message, opts);
414
+ }
415
+ return new ConflictError(message, opts);
416
+ case 429: {
417
+ const header = response.headers.get("retry-after");
418
+ const retryAfter = header !== null && header.trim() !== "" ? Number(header) : null;
419
+ return new RateLimitError(message, {
420
+ ...opts,
421
+ retryAfterSeconds: retryAfter !== null && Number.isFinite(retryAfter) ? retryAfter : null
422
+ });
423
+ }
424
+ case 503:
425
+ return new ServiceUnavailableError(message, opts);
426
+ case 504:
427
+ return new AgentUnavailableError(message, opts);
428
+ default:
429
+ if (response.status >= 500) return new ServiceUnavailableError(message, opts);
430
+ return new SynapError(message, opts);
431
+ }
432
+ }
433
+ function numberOrNull(v) {
434
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
435
+ }
436
+ function withTimeout(ms, external) {
437
+ const controller = new AbortController();
438
+ const onExternalAbort = () => controller.abort(external?.reason);
439
+ const timer = setTimeout(() => {
440
+ controller.abort(new NetworkTimeoutError(`Timed out after ${ms}ms`));
441
+ }, ms);
442
+ timer.unref?.();
443
+ if (external) {
444
+ if (external.aborted) onExternalAbort();
445
+ else external.addEventListener("abort", onExternalAbort, { once: true });
446
+ }
447
+ return {
448
+ signal: controller.signal,
449
+ dispose() {
450
+ clearTimeout(timer);
451
+ external?.removeEventListener("abort", onExternalAbort);
452
+ }
453
+ };
454
+ }
455
+ function sleep(ms, signal) {
456
+ if (ms <= 0) return Promise.resolve();
457
+ return new Promise((resolve, reject) => {
458
+ const timer = setTimeout(() => {
459
+ signal?.removeEventListener("abort", onAbort);
460
+ resolve();
461
+ }, ms);
462
+ function onAbort() {
463
+ clearTimeout(timer);
464
+ reject(signal?.reason ?? new Error("Aborted"));
465
+ }
466
+ signal?.addEventListener("abort", onAbort, { once: true });
467
+ });
468
+ }
469
+
470
+ // src/context/fetch.ts
471
+ var ENDPOINT_BY_SCOPE = {
472
+ user: "context_fetch_user",
473
+ customer: "context_fetch_customer",
474
+ client: "context_fetch_client",
475
+ conversation: "context_fetch_conversation"
476
+ };
477
+ function opt(options, snake, camel) {
478
+ const a = options[snake];
479
+ if (a !== void 0) return a;
480
+ const b = options[camel];
481
+ return b === void 0 ? void 0 : b;
482
+ }
483
+ function buildFetchRequest(scope, options) {
484
+ const userId = opt(options, "user_id", "userId");
485
+ const customerId = opt(options, "customer_id", "customerId");
486
+ const conversationId = opt(options, "conversation_id", "conversationId");
487
+ const searchQuery = opt(options, "search_query", "searchQuery");
488
+ const maxResults = opt(options, "max_results", "maxResults") ?? 10;
489
+ const precisionLevel = opt(options, "precision_level", "precisionLevel") ?? "high";
490
+ const mode = options.mode ?? "fast";
491
+ if (searchQuery !== void 0 && !Array.isArray(searchQuery)) {
492
+ throw new InvalidInputError("search_query must be an array when provided");
493
+ }
494
+ if (options.types !== void 0 && !Array.isArray(options.types)) {
495
+ throw new InvalidInputError("types must be an array when provided");
496
+ }
497
+ const body = {
498
+ conversation_id: conversationId ?? null,
499
+ search_query: searchQuery ?? [],
500
+ max_results: maxResults,
501
+ types: options.types ?? ["all"],
502
+ mode
503
+ };
504
+ if (precisionLevel !== "high") body["precision_level"] = precisionLevel;
505
+ if (options.include_conversation_context === false) {
506
+ body["include_conversation_context"] = false;
507
+ }
508
+ switch (scope) {
509
+ case "user":
510
+ if (!userId) throw new InvalidInputError("user_id is required");
511
+ body["user_id"] = userId;
512
+ if (customerId !== void 0) body["customer_id"] = customerId;
513
+ break;
514
+ case "customer":
515
+ if (!customerId) throw new InvalidInputError("customer_id is required");
516
+ body["customer_id"] = customerId;
517
+ break;
518
+ case "conversation":
519
+ if (!conversationId) throw new InvalidInputError("conversation_id is required");
520
+ if (userId !== void 0) body["user_id"] = userId;
521
+ if (customerId !== void 0) body["customer_id"] = customerId;
522
+ break;
523
+ case "client":
524
+ break;
525
+ }
526
+ return { endpoint: ENDPOINT_BY_SCOPE[scope], body };
527
+ }
528
+ async function fetchContext(transport, scope, options = {}) {
529
+ const { endpoint, body } = buildFetchRequest(scope, options);
530
+ const result = await transport.request(endpoint, { body });
531
+ return result?.["context"] ?? {};
532
+ }
533
+
534
+ // src/context/normalize.ts
535
+ function pickDefined(...values) {
536
+ for (const v of values) if (v !== void 0) return v;
537
+ return void 0;
538
+ }
539
+ function pick(...values) {
540
+ return pickDefined(...values);
541
+ }
542
+ var obj = (v) => v ?? {};
543
+ function normalizeTemporalFields(item = {}) {
544
+ return {
545
+ eventDate: pick(item["eventDate"], item.event_date, null),
546
+ validUntil: pick(item["validUntil"], item.valid_until, null),
547
+ temporalCategory: pick(item["temporalCategory"], item.temporal_category, null),
548
+ temporalConfidence: pick(item["temporalConfidence"], item.temporal_confidence, 0)
549
+ };
550
+ }
551
+ function normalizeFact(item = {}) {
552
+ return {
553
+ id: item.id || "",
554
+ content: item.content || "",
555
+ confidence: pick(item.confidence, 0),
556
+ source: item.source || "",
557
+ extractedAt: pick(item["extractedAt"], item.extracted_at, null),
558
+ metadata: obj(item.metadata),
559
+ ...normalizeTemporalFields(item)
560
+ };
561
+ }
562
+ function normalizePreference(item = {}) {
563
+ return {
564
+ id: item.id || "",
565
+ category: item["category"] || "",
566
+ content: item.content || "",
567
+ strength: pick(item["strength"], item.confidence, 0),
568
+ source: item.source || "",
569
+ extractedAt: pick(item["extractedAt"], item.extracted_at, null),
570
+ metadata: obj(item.metadata),
571
+ ...normalizeTemporalFields(item)
572
+ };
573
+ }
574
+ function normalizeEpisode(item = {}) {
575
+ return {
576
+ id: item.id || "",
577
+ summary: pick(item["summary"], item.content, ""),
578
+ occurredAt: pick(item["occurredAt"], item["occurred_at"], null),
579
+ significance: pick(item["significance"], item.confidence, 0),
580
+ participants: item["participants"] || [],
581
+ metadata: obj(item.metadata),
582
+ ...normalizeTemporalFields(item)
583
+ };
584
+ }
585
+ function normalizeEmotion(item = {}) {
586
+ return {
587
+ id: item.id || "",
588
+ emotionType: pick(item["emotionType"], item["emotion_type"], ""),
589
+ intensity: pick(item["intensity"], item.confidence, 0),
590
+ detectedAt: pick(item["detectedAt"], item["detected_at"], null),
591
+ context: item["context"] || "",
592
+ metadata: obj(item.metadata),
593
+ ...normalizeTemporalFields(item)
594
+ };
595
+ }
596
+ function normalizeTemporalEvent(item = {}) {
597
+ return {
598
+ id: item.id || "",
599
+ content: item.content || "",
600
+ eventDate: pick(item["eventDate"], item.event_date, null),
601
+ validUntil: pick(item["validUntil"], item.valid_until, null),
602
+ temporalCategory: pick(item["temporalCategory"], item.temporal_category, ""),
603
+ temporalConfidence: pick(item["temporalConfidence"], item.temporal_confidence, 0),
604
+ confidence: pick(item.confidence, 0),
605
+ source: item.source || "",
606
+ extractedAt: pick(item["extractedAt"], item.extracted_at, null),
607
+ metadata: obj(item.metadata)
608
+ };
609
+ }
610
+ function normalizeContextMetadata(metadata = {}) {
611
+ return {
612
+ correlationId: pick(metadata["correlationId"], metadata["correlation_id"], ""),
613
+ ttlSeconds: pick(metadata["ttlSeconds"], metadata["ttl_seconds"], 0),
614
+ source: metadata["source"] || "unknown",
615
+ retrievedAt: pick(metadata["retrievedAt"], metadata["retrieved_at"], null),
616
+ compactionApplied: pick(
617
+ metadata["compactionApplied"],
618
+ metadata["compaction_applied"],
619
+ null
620
+ )
621
+ };
622
+ }
623
+ function normalizeConversationContext(value) {
624
+ if (!value) return null;
625
+ return {
626
+ summary: value.summary || null,
627
+ currentState: pick(value["currentState"], value.current_state, {}),
628
+ keyExtractions: pick(value["keyExtractions"], value.key_extractions, {}),
629
+ recentTurns: pick(value["recentTurns"], value.recent_turns, []),
630
+ compactionId: pick(value["compactionId"], value.compaction_id, null),
631
+ compactedAt: pick(value["compactedAt"], value.compacted_at, null),
632
+ conversationId: pick(value["conversationId"], value.conversation_id, null)
633
+ };
634
+ }
635
+ function normalizeContextResponse(result = {}) {
636
+ const context = result["context"] ?? result;
637
+ return {
638
+ facts: (context.facts || []).map((i) => normalizeFact(i)),
639
+ preferences: (context.preferences || []).map((i) => normalizePreference(i)),
640
+ episodes: (context.episodes || []).map((i) => normalizeEpisode(i)),
641
+ emotions: (context.emotions || []).map((i) => normalizeEmotion(i)),
642
+ temporalEvents: (pick(context["temporalEvents"], context.temporal_events, []) || []).map((i) => normalizeTemporalEvent(i)),
643
+ conversationContext: normalizeConversationContext(
644
+ pick(
645
+ context["conversationContext"],
646
+ context.conversation_context,
647
+ null
648
+ )
649
+ ),
650
+ metadata: normalizeContextMetadata(obj(context.metadata)),
651
+ rawResponse: pick(context["rawResponse"], context["raw_response"], {})
652
+ };
653
+ }
654
+
655
+ // src/context/flatten.ts
656
+ var s = (v) => typeof v === "string" ? v : v == null ? "" : String(v);
657
+ var n = (v, d = 0) => typeof v === "number" ? v : d;
658
+ var o = (v) => v ?? {};
659
+ var dt = (v) => v ? String(v) : null;
660
+ function temporal(item) {
661
+ return {
662
+ event_date: dt(item["event_date"]),
663
+ valid_until: dt(item["valid_until"]),
664
+ temporal_category: item["temporal_category"] ?? null,
665
+ temporal_confidence: n(item["temporal_confidence"], 0)
666
+ };
667
+ }
668
+ function flattenContextItems(context) {
669
+ const items = [];
670
+ for (const f of context.facts ?? []) {
671
+ items.push({
672
+ id: s(f["id"]),
673
+ memory: s(f["content"]),
674
+ score: n(f["confidence"]),
675
+ source: s(f["source"]),
676
+ metadata: o(f["metadata"]),
677
+ context_type: "fact",
678
+ ...temporal(f)
679
+ });
680
+ }
681
+ for (const p of context.preferences ?? []) {
682
+ items.push({
683
+ id: s(p["id"]),
684
+ memory: s(p["content"]),
685
+ score: n(p["strength"]),
686
+ source: s(p["source"]),
687
+ metadata: o(p["metadata"]),
688
+ context_type: "preference",
689
+ ...temporal(p)
690
+ });
691
+ }
692
+ for (const e of context.episodes ?? []) {
693
+ items.push({
694
+ id: s(e["id"]),
695
+ memory: s(e["summary"]),
696
+ score: n(e["significance"]),
697
+ metadata: o(e["metadata"]),
698
+ context_type: "episode",
699
+ ...temporal(e)
700
+ });
701
+ }
702
+ for (const m of context.emotions ?? []) {
703
+ items.push({
704
+ id: s(m["id"]),
705
+ memory: s(m["context"]),
706
+ score: n(m["intensity"]),
707
+ metadata: o(m["metadata"]),
708
+ context_type: "emotion",
709
+ ...temporal(m)
710
+ });
711
+ }
712
+ for (const t of context.temporal_events ?? []) {
713
+ items.push({
714
+ id: s(t["id"]),
715
+ memory: s(t["content"]),
716
+ score: n(t["temporal_confidence"]),
717
+ source: s(t["source"]),
718
+ metadata: o(t["metadata"]),
719
+ context_type: "temporal_event",
720
+ ...temporal(t)
721
+ });
722
+ }
723
+ return items;
724
+ }
725
+
726
+ // src/memories/interface.ts
727
+ var DOCUMENT_TYPES = [
728
+ "ai-chat-conversation",
729
+ "document",
730
+ "email",
731
+ "pdf",
732
+ "image",
733
+ "audio",
734
+ "meeting-transcript"
735
+ ];
736
+ var INGEST_MODES = ["fast", "long-range"];
737
+ var DEFAULT_DOCUMENT_TYPE = "ai-chat-conversation";
738
+ var DEFAULT_INGEST_MODE = "long-range";
739
+ function either(o2, snake, camel) {
740
+ const a = o2[snake];
741
+ if (a !== void 0) return a;
742
+ const b = o2[camel];
743
+ return b === void 0 ? void 0 : b;
744
+ }
745
+ function isoOrNull(v) {
746
+ if (v === void 0 || v === null) return null;
747
+ return v instanceof Date ? v.toISOString() : v;
748
+ }
749
+ function buildCreateBody(options) {
750
+ if (!options.document) throw new InvalidInputError("document is required");
751
+ const o2 = options;
752
+ const documentType = either(o2, "document_type", "documentType") ?? DEFAULT_DOCUMENT_TYPE;
753
+ const mode = options.mode ?? DEFAULT_INGEST_MODE;
754
+ if (!DOCUMENT_TYPES.includes(documentType)) {
755
+ throw new InvalidInputError(
756
+ `Invalid document_type '${documentType}'. Expected one of: ${DOCUMENT_TYPES.join(", ")}`
757
+ );
758
+ }
759
+ if (!INGEST_MODES.includes(mode)) {
760
+ throw new InvalidInputError(
761
+ `Invalid mode '${mode}'. Expected one of: ${INGEST_MODES.join(", ")}`
762
+ );
763
+ }
764
+ return {
765
+ document: options.document,
766
+ document_type: documentType,
767
+ document_id: either(o2, "document_id", "documentId") ?? null,
768
+ document_created_at: isoOrNull(
769
+ either(o2, "document_created_at", "documentCreatedAt")
770
+ ),
771
+ user_id: either(o2, "user_id", "userId") ?? null,
772
+ customer_id: either(o2, "customer_id", "customerId") ?? null,
773
+ mode,
774
+ metadata: options.metadata ?? {}
775
+ };
776
+ }
777
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "error", "cancelled"]);
778
+ function createMemoriesInterface(transport) {
779
+ return {
780
+ async create(options) {
781
+ return transport.request("memories_create", { body: buildCreateBody(options) });
782
+ },
783
+ async batch_create(options) {
784
+ const docs = options.documents;
785
+ if (!Array.isArray(docs) || docs.length === 0) {
786
+ throw new InvalidInputError("documents must be a non-empty array");
787
+ }
788
+ const failFast = options.fail_fast ?? options.failFast ?? false;
789
+ return transport.request("memories_batch", {
790
+ body: { documents: docs.map(buildCreateBody), fail_fast: failFast }
791
+ });
792
+ },
793
+ async create_from_file(options) {
794
+ const userId = options.user_id ?? options.userId;
795
+ const customerId = options.customer_id ?? options.customerId;
796
+ if (!userId) throw new InvalidInputError("user_id is required");
797
+ if (!customerId) throw new InvalidInputError("customer_id is required");
798
+ const filePath = options.file_path ?? options.filePath;
799
+ const sources = [filePath, options.file, options.text].filter((v) => v !== void 0);
800
+ if (sources.length === 0) {
801
+ throw new InvalidInputError("One of file_path, file, or text must be provided.");
802
+ }
803
+ if (sources.length > 1) {
804
+ throw new InvalidInputError(
805
+ "Provide exactly one of file_path, file, or text, not several."
806
+ );
807
+ }
808
+ const form = new FormData();
809
+ form.set("user_id", userId);
810
+ form.set("customer_id", customerId);
811
+ form.set("relationship_type", options.relationship_type ?? options.relationshipType ?? "b2c");
812
+ form.set("mode", String(options.mode ?? DEFAULT_INGEST_MODE));
813
+ const documentType = options.document_type ?? options.documentType;
814
+ if (documentType) form.set("document_type", String(documentType));
815
+ if (options.metadata !== void 0 && Object.keys(options.metadata).length > 0) {
816
+ form.set("metadata", JSON.stringify(options.metadata));
817
+ }
818
+ if (filePath !== void 0) {
819
+ const { readFile } = await import("fs/promises");
820
+ const { basename } = await import("path");
821
+ const bytes = await readFile(filePath);
822
+ form.set("file", new Blob([bytes]), basename(filePath));
823
+ } else if (options.file !== void 0) {
824
+ const blob = options.file instanceof Blob ? options.file : new Blob([options.file]);
825
+ form.set("file", blob, options.filename ?? "upload");
826
+ } else {
827
+ form.set("text", String(options.text));
828
+ }
829
+ return transport.request("memories_upload", {
830
+ body: form,
831
+ rawBody: true
832
+ });
833
+ },
834
+ async get(memoryId) {
835
+ if (!memoryId) throw new InvalidInputError("memory_id is required");
836
+ return transport.request("memories_get", { pathParams: { memory_id: memoryId } });
837
+ },
838
+ async update(options) {
839
+ const memoryId = options.memory_id ?? options.memoryId;
840
+ if (!memoryId) throw new InvalidInputError("memory_id is required");
841
+ if (!options.document) throw new InvalidInputError("document is required");
842
+ const body = {
843
+ document: options.document,
844
+ merge_strategy: options.merge_strategy ?? options.mergeStrategy ?? "smart-merge"
845
+ };
846
+ const dt2 = options.document_type ?? options.documentType;
847
+ if (dt2 !== void 0) body["document_type"] = dt2;
848
+ if (options.metadata !== void 0) body["metadata"] = options.metadata;
849
+ return transport.request("memories_update", {
850
+ pathParams: { memory_id: memoryId },
851
+ body
852
+ });
853
+ },
854
+ async delete(memoryId) {
855
+ if (!memoryId) {
856
+ throw new InvalidInputError(
857
+ "memory_id is required. Deleting by user is no longer supported: the previous implementation tracked ids in process memory, so it silently deleted nothing in any serverless or multi-process deployment while reporting success."
858
+ );
859
+ }
860
+ return transport.request("memories_delete", { pathParams: { memory_id: memoryId } });
861
+ },
862
+ async status(ingestionId) {
863
+ if (!ingestionId) throw new InvalidInputError("ingestion_id is required");
864
+ return transport.request("memories_status", {
865
+ pathParams: { ingestion_id: ingestionId }
866
+ });
867
+ },
868
+ async wait_for_completion(ingestionId, options = {}) {
869
+ if (!ingestionId) throw new InvalidInputError("ingestion_id is required");
870
+ const timeoutMs = (options.timeout_seconds ?? options.timeoutSeconds ?? 300) * 1e3;
871
+ const pollMs = (options.poll_interval_seconds ?? options.pollIntervalSeconds ?? 2) * 1e3;
872
+ const deadline = Date.now() + timeoutMs;
873
+ let last = {};
874
+ for (; ; ) {
875
+ last = await this.status(ingestionId);
876
+ const status = String(last["status"] ?? "").toLowerCase();
877
+ if (TERMINAL_STATUSES.has(status)) return last;
878
+ if (Date.now() >= deadline) {
879
+ return last;
880
+ }
881
+ await sleep2(Math.min(pollMs, Math.max(0, deadline - Date.now())), options.signal);
882
+ }
883
+ }
884
+ };
885
+ }
886
+ function sleep2(ms, signal) {
887
+ if (ms <= 0) return Promise.resolve();
888
+ return new Promise((resolve, reject) => {
889
+ const timer = setTimeout(() => {
890
+ signal?.removeEventListener("abort", onAbort);
891
+ resolve();
892
+ }, ms);
893
+ function onAbort() {
894
+ clearTimeout(timer);
895
+ reject(signal?.reason ?? new Error("Aborted"));
896
+ }
897
+ signal?.addEventListener("abort", onAbort, { once: true });
898
+ });
899
+ }
900
+
901
+ // src/util/validators.ts
902
+ var INSTANCE_ID_RE = /^inst_[0-9a-fA-F]{16}$/;
903
+ var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
904
+ function validateConversationId(conversationId) {
905
+ if (conversationId === void 0 || conversationId === null || conversationId === "") return;
906
+ if (typeof conversationId !== "string" || !UUID_RE.test(conversationId)) {
907
+ throw new InvalidConversationIdError(`Invalid conversation ID: ${String(conversationId)}`);
908
+ }
909
+ }
910
+ function validateInstanceId(instanceId) {
911
+ if (instanceId === void 0 || instanceId === null || instanceId === "") return;
912
+ if (typeof instanceId !== "string" || !INSTANCE_ID_RE.test(instanceId)) {
913
+ throw new InvalidInstanceIdError(`Invalid instance ID: ${String(instanceId)}`);
914
+ }
915
+ }
916
+
917
+ // src/conversation/interface.ts
918
+ function either2(o2, snake, camel) {
919
+ const a = o2[snake];
920
+ if (a !== void 0) return a;
921
+ const b = o2[camel];
922
+ return b === void 0 ? void 0 : b;
923
+ }
924
+ function iso(v) {
925
+ if (v === void 0) return void 0;
926
+ return v instanceof Date ? v.toISOString() : v;
927
+ }
928
+ function buildRecordMessageBody(options) {
929
+ const o2 = options;
930
+ const conversationId = either2(o2, "conversation_id", "conversationId");
931
+ const userId = either2(o2, "user_id", "userId");
932
+ const customerId = either2(o2, "customer_id", "customerId");
933
+ if (!conversationId) throw new InvalidInputError("conversation_id is required");
934
+ validateConversationId(conversationId);
935
+ if (!options.role) throw new InvalidInputError("role is required");
936
+ if (options.content === void 0 || options.content === null) {
937
+ throw new InvalidInputError("content is required");
938
+ }
939
+ if (!userId) throw new InvalidInputError("user_id is required");
940
+ if (!customerId) throw new InvalidInputError("customer_id is required");
941
+ const body = {
942
+ conversation_id: conversationId,
943
+ role: options.role,
944
+ content: options.content,
945
+ user_id: userId,
946
+ customer_id: customerId,
947
+ // Python always sends this, defaulting to {}.
948
+ metadata: options.metadata ?? {}
949
+ };
950
+ const sessionId = either2(o2, "session_id", "sessionId");
951
+ if (sessionId !== void 0) body["session_id"] = sessionId;
952
+ return body;
953
+ }
954
+ function buildIngestTranscriptBody(options) {
955
+ const o2 = options;
956
+ const conversationId = either2(o2, "conversation_id", "conversationId");
957
+ const userId = either2(o2, "user_id", "userId");
958
+ if (!conversationId) throw new InvalidInputError("conversation_id is required");
959
+ if (!userId) throw new InvalidInputError("user_id is required");
960
+ if (options.transcript === void 0 || options.transcript === null) {
961
+ throw new InvalidInputError("transcript is required");
962
+ }
963
+ const body = {
964
+ conversation_id: conversationId,
965
+ user_id: userId,
966
+ transcript: options.transcript
967
+ };
968
+ const customerId = either2(o2, "customer_id", "customerId");
969
+ const conversationType = either2(o2, "conversation_type", "conversationType");
970
+ const startedAt = iso(either2(o2, "started_at", "startedAt"));
971
+ const endedAt = iso(either2(o2, "ended_at", "endedAt"));
972
+ if (customerId !== void 0) body["customer_id"] = customerId;
973
+ if (conversationType !== void 0) body["conversation_type"] = conversationType;
974
+ if (options.analysis !== void 0) body["analysis"] = options.analysis;
975
+ if (options.metadata !== void 0) body["metadata"] = options.metadata;
976
+ if (startedAt !== void 0) body["started_at"] = startedAt;
977
+ if (endedAt !== void 0) body["ended_at"] = endedAt;
978
+ return body;
979
+ }
980
+ function buildCompactBody(options) {
981
+ const o2 = options;
982
+ const conversationId = either2(o2, "conversation_id", "conversationId");
983
+ if (!conversationId) throw new InvalidInputError("conversation_id is required");
984
+ validateConversationId(conversationId);
985
+ const body = { conversation_id: conversationId, force: options.force ?? false };
986
+ const strategy = options.strategy ?? either2(o2, "compaction_level", "compactionLevel");
987
+ const targetTokens = either2(o2, "target_tokens", "targetTokens");
988
+ if (strategy !== void 0) body["strategy"] = strategy;
989
+ if (targetTokens !== void 0) body["target_tokens"] = targetTokens;
990
+ return body;
991
+ }
992
+ var CompactionSubscribers = class {
993
+ #byConversation = /* @__PURE__ */ new Map();
994
+ add(conversationId, callback) {
995
+ const list = this.#byConversation.get(conversationId);
996
+ if (list === void 0) this.#byConversation.set(conversationId, [callback]);
997
+ else list.push(callback);
998
+ let unsubscribed = false;
999
+ return () => {
1000
+ if (unsubscribed) return;
1001
+ unsubscribed = true;
1002
+ const current = this.#byConversation.get(conversationId);
1003
+ if (current === void 0) return;
1004
+ const at = current.indexOf(callback);
1005
+ if (at !== -1) current.splice(at, 1);
1006
+ if (current.length === 0) this.#byConversation.delete(conversationId);
1007
+ };
1008
+ }
1009
+ removeAll(conversationId) {
1010
+ if (conversationId === void 0) {
1011
+ let count = 0;
1012
+ for (const list2 of this.#byConversation.values()) count += list2.length;
1013
+ this.#byConversation.clear();
1014
+ return count;
1015
+ }
1016
+ const list = this.#byConversation.get(conversationId);
1017
+ if (list === void 0) return 0;
1018
+ this.#byConversation.delete(conversationId);
1019
+ return list.length;
1020
+ }
1021
+ /** Fires subscribers in registration order. A throwing listener must not break the stream reader. */
1022
+ dispatch(conversationId, bundle, onError) {
1023
+ for (const callback of [...this.#byConversation.get(conversationId) ?? []]) {
1024
+ try {
1025
+ const result = callback(bundle);
1026
+ if (result instanceof Promise) result.catch(onError);
1027
+ } catch (error) {
1028
+ onError(error);
1029
+ }
1030
+ }
1031
+ }
1032
+ };
1033
+ function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
1034
+ }) {
1035
+ const convId = (v) => {
1036
+ const id = typeof v === "string" ? v : v.conversation_id ?? v.conversationId;
1037
+ if (!id) throw new InvalidInputError("conversation_id is required");
1038
+ validateConversationId(id);
1039
+ return id;
1040
+ };
1041
+ return {
1042
+ async record_message(options) {
1043
+ const body = buildRecordMessageBody(options);
1044
+ const result = await transport.request("conversations_messages", { body });
1045
+ onWrite({
1046
+ userId: body["user_id"],
1047
+ customerId: body["customer_id"]
1048
+ });
1049
+ return result;
1050
+ },
1051
+ async record_messages_batch(messages) {
1052
+ if (!Array.isArray(messages) || messages.length === 0) {
1053
+ throw new InvalidInputError("messages must be a non-empty array");
1054
+ }
1055
+ const bodies = messages.map(buildRecordMessageBody);
1056
+ const result = await transport.request("conversations_messages_batch", {
1057
+ body: { messages: bodies }
1058
+ });
1059
+ for (const body of bodies) {
1060
+ onWrite({
1061
+ userId: body["user_id"],
1062
+ customerId: body["customer_id"]
1063
+ });
1064
+ }
1065
+ return result;
1066
+ },
1067
+ async ingest_transcript(options) {
1068
+ const body = buildIngestTranscriptBody(options);
1069
+ const result = await transport.request("conversations_ingest", { body });
1070
+ onWrite({
1071
+ userId: body["user_id"],
1072
+ customerId: body["customer_id"]
1073
+ });
1074
+ return result;
1075
+ },
1076
+ context: {
1077
+ async fetch(options = {}) {
1078
+ validateConversationId(
1079
+ options.conversation_id ?? options.conversationId
1080
+ );
1081
+ return fetchContext(transport, "conversation", options);
1082
+ },
1083
+ get_context_for_prompt: async (options = {}) => {
1084
+ const id = convId(options);
1085
+ const result = await transport.request("context_for_prompt", {
1086
+ pathParams: { conversation_id: id },
1087
+ query: { style: options.style ?? "structured" }
1088
+ });
1089
+ return result?.["context_for_prompt"] ?? result ?? {};
1090
+ },
1091
+ async compact(options) {
1092
+ return transport.request("conversations_compact", { body: buildCompactBody(options) });
1093
+ },
1094
+ get_compacted: async (options) => {
1095
+ const id = convId(options);
1096
+ const query = { format: options.format ?? "structured" };
1097
+ if (options.version !== void 0) query["version"] = options.version;
1098
+ try {
1099
+ return await transport.request("conversations_compacted", {
1100
+ pathParams: { conversation_id: id },
1101
+ query
1102
+ });
1103
+ } catch (error) {
1104
+ if (error.code === "context_not_found") return null;
1105
+ throw error;
1106
+ }
1107
+ },
1108
+ async get_compaction_status(options) {
1109
+ return transport.request("conversations_compaction_status", {
1110
+ pathParams: { conversation_id: convId(options) }
1111
+ });
1112
+ },
1113
+ /**
1114
+ * Fire `callback` whenever a `compaction_update` bundle arrives on the
1115
+ * Listen stream for this conversation.
1116
+ *
1117
+ * Requires an active `client.instance.listen()`; without it no bundles
1118
+ * arrive and the callback never fires. Registering before listening is
1119
+ * fine, which is why this does not throw when the stream is down.
1120
+ *
1121
+ * Returns an idempotent unsubscribe thunk, matching Python.
1122
+ */
1123
+ subscribe_to_compaction_updates(conversationId, callback) {
1124
+ if (!conversationId) throw new InvalidInputError("conversation_id is required");
1125
+ if (typeof callback !== "function") throw new InvalidInputError("callback is required");
1126
+ return subscribers.add(conversationId, callback);
1127
+ },
1128
+ /** Remove subscribers for one conversation, or all of them. Returns the count removed. */
1129
+ unsubscribe_all_compaction_updates(conversationId) {
1130
+ return subscribers.removeAll(conversationId);
1131
+ }
1132
+ }
1133
+ };
1134
+ }
1135
+
1136
+ // src/user/interface.ts
1137
+ function createUserNamespace(transport) {
1138
+ return {
1139
+ async get_profile(options) {
1140
+ const userId = typeof options === "string" ? options : options.user_id ?? options.userId;
1141
+ if (!userId) throw new InvalidInputError("user_id is required");
1142
+ const customerId = typeof options === "string" ? void 0 : options.customer_id ?? options.customerId;
1143
+ return transport.request("user_profile", {
1144
+ pathParams: { user_id: userId },
1145
+ // Sent only when provided, matching Python. Note the server returns
1146
+ // 500 rather than a clean error if it needs one and does not get it.
1147
+ ...customerId !== void 0 ? { query: { customer_id: customerId } } : {}
1148
+ });
1149
+ },
1150
+ context: { async fetch(options = {}) {
1151
+ return fetchContext(transport, "user", options);
1152
+ } }
1153
+ };
1154
+ }
1155
+
1156
+ // src/credits/interface.ts
1157
+ function createCreditsNamespace(transport) {
1158
+ return {
1159
+ async get_balance() {
1160
+ return transport.request("credits_balance");
1161
+ },
1162
+ async get_ledger(options = {}) {
1163
+ return transport.request("credits_ledger", {
1164
+ query: { limit: options.limit ?? 50, offset: options.offset ?? 0 }
1165
+ });
1166
+ },
1167
+ async estimate(options) {
1168
+ return transport.request("credits_estimate", {
1169
+ body: {
1170
+ metric_type: options.metric_type ?? options.metricType ?? null,
1171
+ units: options.units ?? null,
1172
+ item_count: options.item_count ?? options.itemCount ?? null,
1173
+ endpoint: options.endpoint ?? null,
1174
+ mode: options.mode ?? null
1175
+ }
1176
+ });
1177
+ },
1178
+ async redeem(code) {
1179
+ if (!code) throw new InvalidInputError("code is required");
1180
+ return transport.request("credits_redeem", { body: { code } });
1181
+ }
1182
+ };
1183
+ }
1184
+
1185
+ // src/cache/interface.ts
1186
+ function createCacheNamespace(cache) {
1187
+ return {
1188
+ clear: () => cache.clear(),
1189
+ clear_user: (userId) => {
1190
+ if (!userId) throw new InvalidInputError("user_id is required");
1191
+ cache.dropEntity(userId);
1192
+ },
1193
+ clear_customer: (customerId) => {
1194
+ if (!customerId) throw new InvalidInputError("customer_id is required");
1195
+ cache.dropEntity(customerId);
1196
+ },
1197
+ stats: () => ({ bundles: cache.size, items: cache.itemCount })
1198
+ };
1199
+ }
1200
+
1201
+ // src/instance/interface.ts
1202
+ var DEFAULT_GRPC_HOST = "synap-cloud-prod.maximem.ai";
1203
+ var DEFAULT_GRPC_PORT = 443;
1204
+ function envPort() {
1205
+ const raw = getEnv("SYNAP_GRPC_PORT");
1206
+ if (raw === void 0 || raw === "") return void 0;
1207
+ const parsed = Number(raw);
1208
+ return Number.isFinite(parsed) ? parsed : void 0;
1209
+ }
1210
+ function envTls() {
1211
+ const raw = getEnv("SYNAP_GRPC_USE_TLS");
1212
+ if (raw === void 0 || raw === "") return void 0;
1213
+ return !["0", "false", "no", "off"].includes(raw.trim().toLowerCase());
1214
+ }
1215
+ function createInstanceNamespace(credentials, cache) {
1216
+ let client = null;
1217
+ let dispatchCompaction = null;
1218
+ const internals = {
1219
+ onCompactionUpdate(conversationId, bundle) {
1220
+ dispatchCompaction?.(conversationId, bundle);
1221
+ },
1222
+ setCompactionDispatcher(dispatch) {
1223
+ dispatchCompaction = dispatch;
1224
+ },
1225
+ isListening() {
1226
+ return client !== null && client.isConnected;
1227
+ }
1228
+ };
1229
+ const namespace = {
1230
+ async listen(options = {}) {
1231
+ if (client !== null) {
1232
+ throw new ListeningAlreadyActiveError("Listening is already active on this instance");
1233
+ }
1234
+ const { GrpcStreamClient } = await import("./stream-client-ME5N5NK4.js");
1235
+ const onContext = options.on_context ?? options.onContext;
1236
+ const onDisconnect = options.on_disconnect ?? options.onDisconnect;
1237
+ const onReconnect = options.on_reconnect ?? options.onReconnect;
1238
+ let reconnectAttempts = 0;
1239
+ const created = new GrpcStreamClient(credentials(), cache, {
1240
+ host: options.host ?? getEnv("SYNAP_GRPC_HOST") ?? DEFAULT_GRPC_HOST,
1241
+ port: options.port ?? envPort() ?? DEFAULT_GRPC_PORT,
1242
+ useTls: options.use_tls ?? options.useTls ?? envTls() ?? true,
1243
+ ...onContext !== void 0 ? { onContext } : {},
1244
+ onCompactionUpdate: (conversationId, bundle) => {
1245
+ internals.onCompactionUpdate(conversationId, bundle);
1246
+ },
1247
+ onStateChange: (state) => {
1248
+ if (state === "reconnecting") {
1249
+ reconnectAttempts += 1;
1250
+ onReconnect?.(reconnectAttempts);
1251
+ } else if (state === "disconnected") {
1252
+ onDisconnect?.("stream disconnected");
1253
+ }
1254
+ }
1255
+ });
1256
+ try {
1257
+ await created.connect();
1258
+ } catch (error) {
1259
+ if (error instanceof SynapError) throw error;
1260
+ throw new SynapError(
1261
+ `Could not open the Synap anticipation stream: ${error?.message ?? String(error)}`,
1262
+ { cause: error }
1263
+ );
1264
+ }
1265
+ client = created;
1266
+ },
1267
+ async stop_listening() {
1268
+ if (client === null) return;
1269
+ const active = client;
1270
+ client = null;
1271
+ await active.disconnect();
1272
+ },
1273
+ async send_message(options) {
1274
+ if (client === null) throw new ListeningNotActiveError("Listening is not active");
1275
+ if (options.content === void 0) {
1276
+ throw new ListeningNotActiveError("content is required");
1277
+ }
1278
+ client.sendConversationEvent({
1279
+ event_type: options.event_type ?? options.eventType ?? "user_message",
1280
+ content: options.content,
1281
+ role: options.role ?? "user",
1282
+ conversation_id: options.conversation_id ?? options.conversationId ?? "",
1283
+ user_id: options.user_id ?? options.userId ?? "",
1284
+ customer_id: options.customer_id ?? options.customerId ?? "",
1285
+ session_id: options.session_id ?? options.sessionId ?? "",
1286
+ metadata: options.metadata ?? {},
1287
+ timestamp_ms: Date.now(),
1288
+ search_queries: options.search_queries ?? options.searchQueries ?? [],
1289
+ context_types: options.context_types ?? options.contextTypes ?? []
1290
+ });
1291
+ },
1292
+ async record_thinking(options) {
1293
+ if (client === null) throw new ListeningNotActiveError("Listening is not active");
1294
+ client.sendConversationEvent({
1295
+ // Python emits reasoning as an `agent_thinking` conversation event.
1296
+ event_type: "agent_thinking",
1297
+ content: options.content,
1298
+ role: "assistant",
1299
+ conversation_id: options.conversation_id ?? options.conversationId ?? "",
1300
+ user_id: options.user_id ?? options.userId ?? "",
1301
+ customer_id: options.customer_id ?? options.customerId ?? "",
1302
+ session_id: options.session_id ?? options.sessionId ?? "",
1303
+ metadata: options.metadata ?? {},
1304
+ timestamp_ms: Date.now()
1305
+ });
1306
+ },
1307
+ get is_listening() {
1308
+ return client !== null && client.isConnected;
1309
+ },
1310
+ _internal: internals
1311
+ };
1312
+ return namespace;
1313
+ }
1314
+
1315
+ // src/version.ts
1316
+ var SDK_VERSION = "1.0.0-beta.0";
1317
+
1318
+ // src/context/unified.ts
1319
+ var ITEM_COLLECTIONS = [
1320
+ "facts",
1321
+ "preferences",
1322
+ "episodes",
1323
+ "emotions",
1324
+ "temporal_events"
1325
+ ];
1326
+ function itemId(item) {
1327
+ const id = item.id ?? item.item_id;
1328
+ return typeof id === "string" && id !== "" ? id : void 0;
1329
+ }
1330
+ function mergeScopeResults(scopeResults) {
1331
+ const seen = /* @__PURE__ */ new Set();
1332
+ const out = {
1333
+ facts: [],
1334
+ preferences: [],
1335
+ episodes: [],
1336
+ emotions: [],
1337
+ temporal_events: [],
1338
+ scope_map: {},
1339
+ conversation_context: null,
1340
+ profile: null,
1341
+ conversations: null,
1342
+ formatted_context: "",
1343
+ scopes_queried: [],
1344
+ total_items: 0,
1345
+ metadata: null
1346
+ };
1347
+ for (const [scopeName, response] of scopeResults) {
1348
+ out.scopes_queried.push(scopeName);
1349
+ if (out.metadata === null && response.metadata !== void 0) {
1350
+ out.metadata = response.metadata;
1351
+ }
1352
+ if (out.profile === null && response["profile"] !== void 0 && response["profile"] !== null) {
1353
+ out.profile = response["profile"];
1354
+ }
1355
+ if (out.conversations === null && response["conversations"] !== void 0 && response["conversations"] !== null) {
1356
+ out.conversations = response["conversations"];
1357
+ }
1358
+ for (const collection of ITEM_COLLECTIONS) {
1359
+ const items = response[collection];
1360
+ if (!Array.isArray(items)) continue;
1361
+ for (const item of items) {
1362
+ const id = itemId(item);
1363
+ if (id !== void 0) {
1364
+ if (seen.has(id)) continue;
1365
+ seen.add(id);
1366
+ out.scope_map[id] = scopeName;
1367
+ }
1368
+ out[collection].push(item);
1369
+ }
1370
+ }
1371
+ }
1372
+ out.total_items = ITEM_COLLECTIONS.reduce((n2, c) => n2 + out[c].length, 0);
1373
+ return out;
1374
+ }
1375
+ function oneDecimal(value) {
1376
+ return value.toFixed(1);
1377
+ }
1378
+ function isoDate(value) {
1379
+ if (typeof value !== "string" || value === "") return null;
1380
+ const parsed = new Date(value);
1381
+ if (Number.isNaN(parsed.getTime())) return null;
1382
+ return parsed.toISOString().slice(0, 10);
1383
+ }
1384
+ function numberOf(value) {
1385
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1386
+ }
1387
+ function scopeOf(ctx, item) {
1388
+ const id = itemId(item);
1389
+ return (id !== void 0 ? ctx.scope_map[id] : void 0) ?? "unknown";
1390
+ }
1391
+ function bulletWithExtras(text, extras) {
1392
+ return extras.length > 0 ? `- ${text} [${extras.join(", ")}]` : `- ${text}`;
1393
+ }
1394
+ function formatForPrompt(ctx, options = {}) {
1395
+ const includeScope = options.includeScope ?? false;
1396
+ const includeConversationContext = options.includeConversationContext ?? true;
1397
+ const sections = [];
1398
+ if (ctx.facts.length > 0) {
1399
+ const lines = ctx.facts.map((f) => {
1400
+ let line = `- ${String(f.content ?? "")}`;
1401
+ if (includeScope) line += ` [scope: ${scopeOf(ctx, f)}]`;
1402
+ return line;
1403
+ });
1404
+ sections.push(`### Facts
1405
+ ${lines.join("\n")}`);
1406
+ }
1407
+ if (ctx.preferences.length > 0) {
1408
+ const lines = ctx.preferences.map((p) => {
1409
+ const extras = [];
1410
+ if (includeScope) extras.push(`scope: ${scopeOf(ctx, p)}`);
1411
+ const strength = numberOf(p["strength"]);
1412
+ if (strength !== null && strength !== 0 && strength >= 0.7) {
1413
+ extras.push(`strength: ${oneDecimal(strength)}`);
1414
+ }
1415
+ return bulletWithExtras(String(p.content ?? ""), extras);
1416
+ });
1417
+ sections.push(`### Preferences
1418
+ ${lines.join("\n")}`);
1419
+ }
1420
+ if (ctx.episodes.length > 0) {
1421
+ const lines = ctx.episodes.map((e) => {
1422
+ let line = `- ${String(e["summary"] ?? "")}`;
1423
+ if (includeScope) line += ` [scope: ${scopeOf(ctx, e)}]`;
1424
+ return line;
1425
+ });
1426
+ sections.push(`### Episodes
1427
+ ${lines.join("\n")}`);
1428
+ }
1429
+ if (ctx.emotions.length > 0) {
1430
+ const lines = ctx.emotions.map((em) => {
1431
+ const extras = [];
1432
+ if (includeScope) extras.push(`scope: ${scopeOf(ctx, em)}`);
1433
+ const intensity = numberOf(em["intensity"]);
1434
+ if (intensity !== null && intensity !== 0 && intensity >= 0.5) {
1435
+ extras.push(`intensity: ${oneDecimal(intensity)}`);
1436
+ }
1437
+ const text = `${String(em["emotion_type"] ?? "")}: ${String(em["context"] ?? "")}`;
1438
+ return bulletWithExtras(text, extras);
1439
+ });
1440
+ sections.push(`### Emotions
1441
+ ${lines.join("\n")}`);
1442
+ }
1443
+ if (ctx.temporal_events.length > 0) {
1444
+ const lines = ctx.temporal_events.map((te) => {
1445
+ const extras = [];
1446
+ if (includeScope) extras.push(`scope: ${scopeOf(ctx, te)}`);
1447
+ const validUntil = isoDate(te["valid_until"]);
1448
+ if (validUntil !== null) extras.push(`valid until: ${validUntil}`);
1449
+ return bulletWithExtras(String(te.content ?? ""), extras);
1450
+ });
1451
+ sections.push(`### Temporal Events
1452
+ ${lines.join("\n")}`);
1453
+ }
1454
+ if (includeConversationContext && ctx.conversation_context !== null) {
1455
+ const formatted = ctx.conversation_context["formatted_context"];
1456
+ if (typeof formatted === "string" && formatted !== "") {
1457
+ sections.push(`### Conversation History
1458
+ ${formatted}`);
1459
+ }
1460
+ }
1461
+ const blocks = [];
1462
+ if (sections.length > 0) blocks.push(`## User Context
1463
+ ${sections.join("\n\n")}`);
1464
+ const profileBlock = formatProfileBlock(ctx.profile);
1465
+ if (profileBlock !== null) blocks.push(profileBlock);
1466
+ const conversationsBlock = formatConversationsBlock(ctx.conversations);
1467
+ if (conversationsBlock !== null) blocks.push(conversationsBlock);
1468
+ return blocks.length > 0 ? blocks.join("\n\n") : "";
1469
+ }
1470
+ function formatProfileBlock(profile) {
1471
+ if (profile === null) return null;
1472
+ const attributes = profile["attributes"];
1473
+ const lines = [];
1474
+ if (typeof attributes === "object" && attributes !== null) {
1475
+ for (const [name, attr] of Object.entries(attributes)) {
1476
+ const value = typeof attr === "object" && attr !== null ? attr["value"] : void 0;
1477
+ let valueStr;
1478
+ if (Array.isArray(value)) valueStr = value.map((v) => String(v)).join(", ");
1479
+ else if (value === null || value === void 0) valueStr = "";
1480
+ else valueStr = String(value);
1481
+ lines.push(`- ${name}: ${valueStr}`.replace(/\s+$/, ""));
1482
+ }
1483
+ }
1484
+ const bodyParts = [];
1485
+ if (lines.length > 0) bodyParts.push(lines.join("\n"));
1486
+ const overview = profile["overview"];
1487
+ if (typeof overview === "string" && overview !== "") bodyParts.push(overview);
1488
+ if (bodyParts.length === 0) return null;
1489
+ return `## Caller Profile
1490
+ ${bodyParts.join("\n\n")}`;
1491
+ }
1492
+ function formatConversationsBlock(conversations) {
1493
+ if (conversations === null || conversations.length === 0) return null;
1494
+ const callBlocks = conversations.map((conv) => {
1495
+ const when = isoDate(conv["started_at"]) ?? isoDate(conv["last_message_at"]) ?? isoDate(conv["ended_at"]);
1496
+ let header = `### Call on ${when ?? "unknown date"}`;
1497
+ const convType = conv["conversation_type"];
1498
+ if (typeof convType === "string" && convType !== "") header += ` (${convType})`;
1499
+ const callLines = [header];
1500
+ const overview = overviewText(conv);
1501
+ if (overview !== null) callLines.push(`Overview: ${overview}`);
1502
+ const outcome = outcomeText(conv);
1503
+ if (outcome !== null) callLines.push(`Outcome: ${outcome}`);
1504
+ if (overview === null && outcome === null) {
1505
+ callLines.push(`Status: ${String(conv["summary_status"] ?? "pending")}`);
1506
+ }
1507
+ return callLines.join("\n");
1508
+ });
1509
+ return `## Previous Conversations
1510
+ ${callBlocks.join("\n\n")}`;
1511
+ }
1512
+ function overviewText(conv) {
1513
+ const summary = conv["summary"];
1514
+ if (typeof summary !== "object" || summary === null || Array.isArray(summary)) return null;
1515
+ for (const key of ["narrative_summary", "overview", "narrative", "summary"]) {
1516
+ const val = summary[key];
1517
+ if (typeof val === "string" && val.trim() !== "") return val.trim();
1518
+ }
1519
+ return null;
1520
+ }
1521
+ function outcomeText(conv) {
1522
+ const classification = conv["classification"];
1523
+ if (typeof classification !== "object" || classification === null || Array.isArray(classification)) {
1524
+ return null;
1525
+ }
1526
+ const c = classification;
1527
+ const parts = [c["primary_category"], c["subcategory"], c["objective"]].filter((p) => typeof p === "string" && p.trim() !== "");
1528
+ return parts.length > 0 ? parts.join(" / ") : null;
1529
+ }
1530
+
1531
+ // src/tool/as-tool.ts
1532
+ var VALID_SCOPES = ["conversation", "user", "customer", "client", "unified"];
1533
+ var BASE_DESCRIPTION = "Retrieve stored context (facts, preferences, recent episodes, emotions, temporal events) from Synap memory.";
1534
+ function defaultToolDescription(scope) {
1535
+ switch (scope) {
1536
+ case "conversation":
1537
+ return BASE_DESCRIPTION + " Scoped to a specific conversation. Call this when you need facts already established in the current conversation, or to reload context after a topic shift.";
1538
+ case "user":
1539
+ return BASE_DESCRIPTION + " Scoped to the user's long-term memory. Call this when you need to recall who the user is, their preferences, or their history across conversations.";
1540
+ case "customer":
1541
+ return BASE_DESCRIPTION + " Scoped to the customer/organization. Call this when you need facts about the customer org rather than an individual.";
1542
+ case "client":
1543
+ return BASE_DESCRIPTION + " Scoped to the integrating client/product. Call this for product-level knowledge (e.g. policies, documentation).";
1544
+ default:
1545
+ return BASE_DESCRIPTION + " Cross-scope: merges conversation, user, customer, and client memory in one call. Call this at the start of a turn when you need general grounding before responding.";
1546
+ }
1547
+ }
1548
+ function toolInputSchema(scope, options) {
1549
+ const props = {
1550
+ search_query: {
1551
+ type: "array",
1552
+ items: { type: "string" },
1553
+ description: "Optional list of search queries describing what context you need. Free-form natural language is fine. Omit to retrieve the most relevant recent context."
1554
+ },
1555
+ max_results: {
1556
+ type: "integer",
1557
+ minimum: 1,
1558
+ maximum: 50,
1559
+ description: "Maximum items to return (default 10)."
1560
+ },
1561
+ types: {
1562
+ type: "array",
1563
+ items: {
1564
+ type: "string",
1565
+ enum: ["facts", "preferences", "episodes", "emotions", "temporal_events"]
1566
+ },
1567
+ description: "Filter to specific memory categories. Omit to retrieve all."
1568
+ },
1569
+ mode: {
1570
+ type: "string",
1571
+ enum: ["fast", "accurate"],
1572
+ description: "Retrieval mode. 'fast' = low-latency (~50ms); 'accurate' = LLM-decomposed multi-query (~200-500ms). Default 'fast'."
1573
+ },
1574
+ precision_level: {
1575
+ type: "string",
1576
+ enum: ["high", "medium"],
1577
+ description: "Result filtering precision. 'high' (default) applies an extra relevance-refinement pass; 'medium' skips it for faster, less precisely filtered results (recall unaffected)."
1578
+ }
1579
+ };
1580
+ let required = [];
1581
+ if (scope === "conversation" && !options.hasConversationId) {
1582
+ props["conversation_id"] = {
1583
+ type: "string",
1584
+ description: "The conversation id to fetch context for."
1585
+ };
1586
+ required = ["conversation_id"];
1587
+ }
1588
+ return { type: "object", properties: props, required, additionalProperties: false };
1589
+ }
1590
+ var RESULT_COLLECTIONS = ["facts", "preferences", "episodes", "emotions", "temporal_events"];
1591
+ async function invokeScopeFetch(params) {
1592
+ const { target, scope, userId, customerId, conversationId, callArgs } = params;
1593
+ const searchQuery = callArgs["search_query"];
1594
+ const rawMax = callArgs["max_results"];
1595
+ const maxResults = typeof rawMax === "number" && rawMax !== 0 ? Math.trunc(rawMax) : 10;
1596
+ const types = callArgs["types"];
1597
+ const mode = String(callArgs["mode"] ?? "") || "fast";
1598
+ const precisionLevel = String(callArgs["precision_level"] ?? "") || "high";
1599
+ const callConvId = (typeof callArgs["conversation_id"] === "string" && callArgs["conversation_id"] !== "" ? callArgs["conversation_id"] : void 0) ?? conversationId;
1600
+ const shared = {
1601
+ search_query: searchQuery,
1602
+ max_results: maxResults,
1603
+ types,
1604
+ mode,
1605
+ precision_level: precisionLevel
1606
+ };
1607
+ if (scope === "unified") {
1608
+ const response2 = await target.fetch({
1609
+ conversation_id: callConvId,
1610
+ user_id: userId,
1611
+ customer_id: customerId,
1612
+ ...shared
1613
+ });
1614
+ return {
1615
+ formatted_context: response2.formatted_context,
1616
+ scopes_queried: response2.scopes_queried,
1617
+ total_items: response2.total_items
1618
+ };
1619
+ }
1620
+ let response;
1621
+ if (scope === "conversation") {
1622
+ if (callConvId === void 0 || callConvId === "") {
1623
+ return { error: "conversation_id is required", items: [] };
1624
+ }
1625
+ response = await target.conversation.context.fetch({
1626
+ conversation_id: callConvId,
1627
+ ...shared,
1628
+ user_id: userId
1629
+ });
1630
+ } else if (scope === "user") {
1631
+ response = await target.user.context.fetch({
1632
+ user_id: userId,
1633
+ conversation_id: callConvId,
1634
+ ...shared,
1635
+ customer_id: customerId
1636
+ });
1637
+ } else if (scope === "customer") {
1638
+ response = await target.customer.context.fetch({
1639
+ customer_id: customerId,
1640
+ conversation_id: callConvId,
1641
+ ...shared
1642
+ });
1643
+ } else {
1644
+ response = await target.client.context.fetch({
1645
+ conversation_id: callConvId,
1646
+ ...shared
1647
+ });
1648
+ }
1649
+ const out = {};
1650
+ for (const collection of RESULT_COLLECTIONS) {
1651
+ out[collection] = Array.isArray(response[collection]) ? response[collection] : [];
1652
+ }
1653
+ return out;
1654
+ }
1655
+ function buildTool(target, options = {}, onWarning = () => {
1656
+ }) {
1657
+ const scope = String(options.scope ?? "user").toLowerCase();
1658
+ if (!VALID_SCOPES.includes(scope)) {
1659
+ const sorted = [...VALID_SCOPES].sort();
1660
+ throw new InvalidInputError(
1661
+ `scope must be one of ['${sorted.join("', '")}'], got '${String(options.scope)}'`
1662
+ );
1663
+ }
1664
+ const userId = options.user_id ?? options.userId;
1665
+ const customerId = options.customer_id ?? options.customerId;
1666
+ const conversationId = options.conversation_id ?? options.conversationId;
1667
+ if (scope === "user" && (userId === void 0 || userId === "")) {
1668
+ throw new InvalidInputError("scope='user' requires user_id");
1669
+ }
1670
+ if (scope === "customer" && (customerId === void 0 || customerId === "")) {
1671
+ throw new InvalidInputError("scope='customer' requires customer_id");
1672
+ }
1673
+ if (scope === "conversation" && (userId === void 0 || userId === "")) {
1674
+ onWarning(
1675
+ "as_tool(scope='conversation') without user_id: the SDK anticipation cache cannot apply per-user filtering. Pass user_id to enable the Section 15 privacy guarantee."
1676
+ );
1677
+ }
1678
+ const style = String(options.style ?? "openai");
1679
+ if (style !== "openai" && style !== "anthropic") {
1680
+ throw new InvalidInputError(`style must be 'openai' or 'anthropic', got '${style}'`);
1681
+ }
1682
+ const toolName = options.name ?? `synap_fetch_${scope}_context`;
1683
+ const toolDesc = options.description ?? defaultToolDescription(scope);
1684
+ const schema = toolInputSchema(scope, { hasConversationId: conversationId !== void 0 });
1685
+ const handler = (callArgs = {}) => invokeScopeFetch({ target, scope, userId, customerId, conversationId, callArgs });
1686
+ if (style === "openai") {
1687
+ return {
1688
+ type: "function",
1689
+ function: { name: toolName, description: toolDesc, parameters: schema },
1690
+ handler
1691
+ };
1692
+ }
1693
+ return { name: toolName, description: toolDesc, input_schema: schema, handler };
1694
+ }
1695
+
1696
+ // src/client.ts
1697
+ var SynapClient = class {
1698
+ // Native-private (`#`), not TypeScript `private`. TS `private` is erased at
1699
+ // compile time, so the field stays enumerable at runtime and a JS consumer
1700
+ // can still reach it. These two were public and made `client.transport
1701
+ // .credentials.apiKey` and the raw cache's `now`/`fire`/`invalidateEntity`
1702
+ // reachable, which is exactly what `client.cache` exists to prevent.
1703
+ #transport;
1704
+ #anticipationCache;
1705
+ user;
1706
+ customer;
1707
+ client;
1708
+ conversation;
1709
+ memories;
1710
+ credits;
1711
+ cache;
1712
+ instance;
1713
+ #closed = false;
1714
+ #initialized = false;
1715
+ #compactionSubscribers;
1716
+ constructor(options = {}) {
1717
+ const credentials = resolveCredentials(options);
1718
+ validateInstanceId(credentials.instanceId);
1719
+ this.#transport = new HttpTransport({
1720
+ credentials,
1721
+ ...options.baseUrl !== void 0 ? { baseUrl: options.baseUrl } : {},
1722
+ ...options.timeouts !== void 0 ? { timeouts: options.timeouts } : {},
1723
+ ...options.retryPolicy !== void 0 ? { retryPolicy: options.retryPolicy } : {},
1724
+ ...options.keepAlive !== void 0 ? { keepAlive: options.keepAlive } : {},
1725
+ ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
1726
+ userAgent: `@maximem/synap-js-sdk/${SDK_VERSION}`
1727
+ });
1728
+ if (options.heartbeat) this.#transport.startHeartbeat();
1729
+ this.#anticipationCache = new AnticipationCache();
1730
+ this.user = createUserNamespace(this.#transport);
1731
+ this.customer = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "customer", o2) } };
1732
+ this.client = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "client", o2) } };
1733
+ this.#compactionSubscribers = new CompactionSubscribers();
1734
+ this.conversation = createConversationNamespace(
1735
+ this.#transport,
1736
+ this.#compactionSubscribers,
1737
+ // Mirrors Python's `_invalidate_anticipation_on_write`, including its
1738
+ // gate: `invalidateEntity` is a no-op unless
1739
+ // SYNAP_SDK_CACHE_INVALIDATE_ON_WRITE is set, so default behaviour is
1740
+ // unchanged. Over-invalidating after a failed write is safe; it only
1741
+ // costs the next lookup a cold fetch.
1742
+ ({ userId, customerId }) => {
1743
+ for (const id of /* @__PURE__ */ new Set([userId, customerId])) {
1744
+ if (id) this.#anticipationCache.invalidateEntity(id);
1745
+ }
1746
+ }
1747
+ );
1748
+ this.memories = createMemoriesInterface(this.#transport);
1749
+ this.credits = createCreditsNamespace(this.#transport);
1750
+ this.cache = createCacheNamespace(this.#anticipationCache);
1751
+ this.instance = createInstanceNamespace(
1752
+ () => this.#transport.currentCredentials(),
1753
+ this.#anticipationCache
1754
+ );
1755
+ this.instance._internal.setCompactionDispatcher((conversationId, bundle) => {
1756
+ this.#compactionSubscribers.dispatch(conversationId, bundle, (error) => {
1757
+ this.#warn(`compaction subscriber threw: ${String(error)}`);
1758
+ });
1759
+ });
1760
+ }
1761
+ // ── Deprecated camelCase surface, carried over from the 0.3.x wrapper ───────
1762
+ // These return the NORMALISED shape, unlike their namespaced equivalents.
1763
+ /** @deprecated Use `client.user.context.fetch()`. Note it returns raw snake_case. */
1764
+ async fetchUserContext(options = {}) {
1765
+ return normalizeContextResponse(await fetchContext(this.#transport, "user", options));
1766
+ }
1767
+ /** @deprecated Use `client.customer.context.fetch()`. */
1768
+ async fetchCustomerContext(options = {}) {
1769
+ return normalizeContextResponse(await fetchContext(this.#transport, "customer", options));
1770
+ }
1771
+ /** @deprecated Use `client.client.context.fetch()`. */
1772
+ async fetchClientContext(options = {}) {
1773
+ return normalizeContextResponse(await fetchContext(this.#transport, "client", options));
1774
+ }
1775
+ /** @deprecated Use `client.conversation.context.get_context_for_prompt()`. */
1776
+ async getContextForPrompt(options = {}) {
1777
+ return this.conversation.context.get_context_for_prompt(options);
1778
+ }
1779
+ /**
1780
+ * @deprecated Use `client.user.context.fetch()` and read the typed collections.
1781
+ *
1782
+ * Flattens every context type into one list, exactly as the 0.3.x bridge did.
1783
+ */
1784
+ async searchMemory(options) {
1785
+ const userId = options.user_id ?? options.userId;
1786
+ if (!userId) throw new InvalidInputError("user_id is required");
1787
+ if (options.query === void 0 || options.query === null) {
1788
+ throw new InvalidInputError("query is required");
1789
+ }
1790
+ const context = await fetchContext(this.#transport, "user", {
1791
+ user_id: userId,
1792
+ ...pick2(options, "customer_id", "customerId") !== void 0 ? { customer_id: pick2(options, "customer_id", "customerId") } : {},
1793
+ ...pick2(options, "conversation_id", "conversationId") !== void 0 ? { conversation_id: pick2(options, "conversation_id", "conversationId") } : {},
1794
+ search_query: [options.query],
1795
+ max_results: pick2(options, "max_results", "maxResults") ?? 10,
1796
+ types: options.types ?? ["all"],
1797
+ ...options.mode !== void 0 ? { mode: options.mode } : {}
1798
+ });
1799
+ const results = flattenContextItems(context);
1800
+ return { results, count: results.length };
1801
+ }
1802
+ /**
1803
+ * @deprecated Use `client.user.context.fetch()` with an empty `search_query`.
1804
+ *
1805
+ * Note the default of 100, not 10: it matches the 0.3.x bridge.
1806
+ */
1807
+ async getMemories(options) {
1808
+ const userId = options.user_id ?? options.userId;
1809
+ if (!userId) throw new InvalidInputError("user_id is required");
1810
+ const context = await fetchContext(this.#transport, "user", {
1811
+ user_id: userId,
1812
+ ...pick2(options, "customer_id", "customerId") !== void 0 ? { customer_id: pick2(options, "customer_id", "customerId") } : {},
1813
+ ...pick2(options, "conversation_id", "conversationId") !== void 0 ? { conversation_id: pick2(options, "conversation_id", "conversationId") } : {},
1814
+ search_query: [],
1815
+ max_results: pick2(options, "max_results", "maxResults") ?? 100,
1816
+ types: options.types ?? ["all"],
1817
+ ...options.mode !== void 0 ? { mode: options.mode } : {}
1818
+ });
1819
+ const results = flattenContextItems(context);
1820
+ return { results, count: results.length };
1821
+ }
1822
+ /** @deprecated Use `client.memories.create()`. */
1823
+ async addMemory(options) {
1824
+ return this.memories.create(options);
1825
+ }
1826
+ /**
1827
+ * @deprecated Use `client.memories.delete(memoryId)`.
1828
+ *
1829
+ * The `{ userId }` form is gone: it relied on a process-local id list and so
1830
+ * deleted nothing in serverless while reporting success (gotcha G-F).
1831
+ */
1832
+ async deleteMemory(options) {
1833
+ return this.memories.delete(options.memory_id ?? options.memoryId ?? "");
1834
+ }
1835
+ /**
1836
+ * Resolve `client_id` and `instance_id` from the API key.
1837
+ *
1838
+ * Mirrors Python's `initialize()`. Calling it is optional here (unlike
1839
+ * Python, which raises "SDK not initialized" from `_ensure_initialized`)
1840
+ * because every HTTP route authenticates on the API key alone. It matters
1841
+ * for the anticipation cache: a bundle shared at client scope is filtered on
1842
+ * `client_id`, and an empty one fails that filter for every bundle. That was
1843
+ * a silent cache-scope bug in Python before whoami bootstrapping existed.
1844
+ *
1845
+ * Best-effort and idempotent. A whoami failure leaves whatever the
1846
+ * constructor resolved and is not fatal.
1847
+ */
1848
+ async initialize() {
1849
+ if (this.#initialized) return;
1850
+ this.#initialized = true;
1851
+ const credentials = this.#transport.currentCredentials();
1852
+ if (credentials.clientId !== "" && credentials.instanceId !== "") return;
1853
+ try {
1854
+ const whoami = await this.#transport.request("whoami", {});
1855
+ const resolvedClientId = String(whoami["client_id"] ?? "");
1856
+ const resolvedInstanceId = String(whoami["instance_id"] ?? "");
1857
+ this.#transport.updateCredentials({
1858
+ ...resolvedClientId !== "" && credentials.clientId === "" ? { clientId: resolvedClientId } : {},
1859
+ ...resolvedInstanceId !== "" && credentials.instanceId === "" ? { instanceId: resolvedInstanceId } : {}
1860
+ });
1861
+ } catch {
1862
+ }
1863
+ }
1864
+ /**
1865
+ * @deprecated Use `initialize()`, which is the Python name. `init()` was a
1866
+ * JS-only spelling with no Python counterpart; it now forwards, so a 0.3.x
1867
+ * startup script keeps working.
1868
+ */
1869
+ async init() {
1870
+ return this.initialize();
1871
+ }
1872
+ /** The resolved instance id. Empty until `initialize()` resolves it. */
1873
+ get instance_id() {
1874
+ return this.#transport.currentCredentials().instanceId;
1875
+ }
1876
+ /** The resolved client id. Empty until `initialize()` resolves it. */
1877
+ get client_id() {
1878
+ return this.#transport.currentCredentials().clientId;
1879
+ }
1880
+ /**
1881
+ * Update configuration before `initialize()`.
1882
+ *
1883
+ * Mirrors Python's `configure()`, including its refusal to reconfigure after
1884
+ * initialization: the transport and cache are already built by then and a
1885
+ * half-applied config is worse than a rejected one.
1886
+ *
1887
+ * The Python-only keys (`storage_path`, `cache_backend`,
1888
+ * `session_timeout_minutes`, `logger`) are accepted and ignored rather than
1889
+ * rejected, because this SDK has no SQLite cache backend and no global
1890
+ * logger to swap. Ignoring them keeps a shared config object portable
1891
+ * between the two SDKs.
1892
+ */
1893
+ configure(options = {}) {
1894
+ if (this.#initialized) {
1895
+ throw new InvalidInputError("Cannot reconfigure after initialization");
1896
+ }
1897
+ if (options.timeouts !== void 0) this.#transport.setTimeouts(options.timeouts);
1898
+ if (options.retryPolicy !== void 0 || options.retry_policy !== void 0) {
1899
+ this.#transport.setRetryPolicy(options.retryPolicy ?? options.retry_policy ?? null);
1900
+ }
1901
+ }
1902
+ /**
1903
+ * Fetch and merge context across every scope with an identifier, in one call.
1904
+ *
1905
+ * Mirrors Python's `sdk.fetch()`. The three behaviours worth knowing:
1906
+ *
1907
+ * - Scope fetches run in parallel and a FAILED scope is dropped with a
1908
+ * warning rather than failing the call, so partial context still gets
1909
+ * returned. The one exception is `InvalidInputError`: a bad request must
1910
+ * surface instead of degrading into a silently-empty context.
1911
+ * - `user_id` is threaded into the conversation-scope sub-fetch so the
1912
+ * anticipation cache can apply its per-user filter.
1913
+ * - The summary-mode parameters go ONLY to the user-scope sub-fetch. The
1914
+ * other three routes return 422 for `context_mode="conversation-summary"`,
1915
+ * and that rejection would be swallowed by the drop-failed-scope rule.
1916
+ */
1917
+ async fetch(options = {}) {
1918
+ const {
1919
+ conversation_id: conversationId,
1920
+ user_id: userId,
1921
+ customer_id: customerId,
1922
+ search_query: searchQuery,
1923
+ max_results: maxResults = 20,
1924
+ types,
1925
+ mode = "fast",
1926
+ precision_level: precisionLevel = "high",
1927
+ include_conversation_context: includeConversationContext = true,
1928
+ scopes,
1929
+ include_scope_labels: includeScopeLabels = false,
1930
+ context_mode: contextMode = "in-conversation",
1931
+ include_profile: includeProfile = true,
1932
+ last_n_conversations: lastNConversations = 1
1933
+ } = options;
1934
+ validateConversationId(conversationId);
1935
+ const wants = (scope) => scopes === void 0 || scopes.includes(scope);
1936
+ const shared = {
1937
+ search_query: searchQuery,
1938
+ max_results: maxResults,
1939
+ types,
1940
+ mode,
1941
+ precision_level: precisionLevel
1942
+ };
1943
+ const labels = [];
1944
+ const tasks = [];
1945
+ if (conversationId !== void 0 && conversationId !== "" && wants("conversation")) {
1946
+ labels.push("conversation");
1947
+ tasks.push(this.conversation.context.fetch({
1948
+ conversation_id: conversationId,
1949
+ ...shared,
1950
+ user_id: userId,
1951
+ customer_id: customerId
1952
+ }));
1953
+ }
1954
+ if (userId !== void 0 && userId !== "" && wants("user")) {
1955
+ labels.push("user");
1956
+ tasks.push(this.user.context.fetch({
1957
+ user_id: userId,
1958
+ conversation_id: conversationId,
1959
+ ...shared,
1960
+ customer_id: customerId,
1961
+ context_mode: contextMode,
1962
+ include_profile: includeProfile,
1963
+ last_n_conversations: lastNConversations
1964
+ }));
1965
+ }
1966
+ if (customerId !== void 0 && customerId !== "" && wants("customer")) {
1967
+ labels.push("customer");
1968
+ tasks.push(this.customer.context.fetch({
1969
+ customer_id: customerId,
1970
+ conversation_id: conversationId,
1971
+ ...shared
1972
+ }));
1973
+ }
1974
+ if (scopes !== void 0 && scopes.includes("client")) {
1975
+ labels.push("client");
1976
+ tasks.push(this.client.context.fetch({
1977
+ conversation_id: conversationId,
1978
+ ...shared
1979
+ }));
1980
+ }
1981
+ if (tasks.length === 0) {
1982
+ const empty = mergeScopeResults([]);
1983
+ return empty;
1984
+ }
1985
+ const settled = await Promise.allSettled(tasks);
1986
+ const successful = [];
1987
+ for (const [i, result] of settled.entries()) {
1988
+ const label = labels[i];
1989
+ if (result.status === "fulfilled") {
1990
+ successful.push([label, result.value]);
1991
+ continue;
1992
+ }
1993
+ if (result.reason instanceof InvalidInputError) throw result.reason;
1994
+ this.#warn(`Scope '${label}' fetch failed in unified fetch (non-fatal): ${String(result.reason)}`);
1995
+ }
1996
+ const merged = mergeScopeResults(successful);
1997
+ if (includeConversationContext && conversationId !== void 0 && conversationId !== "") {
1998
+ try {
1999
+ merged.conversation_context = await this.conversation.context.get_context_for_prompt({
2000
+ conversation_id: conversationId
2001
+ });
2002
+ } catch {
2003
+ }
2004
+ }
2005
+ merged.formatted_context = formatForPrompt(merged, {
2006
+ includeScope: includeScopeLabels,
2007
+ includeConversationContext
2008
+ });
2009
+ return merged;
2010
+ }
2011
+ /**
2012
+ * An LLM-ready tool definition that fetches Synap context.
2013
+ *
2014
+ * Mirrors Python's `as_tool()`. The scope identifiers are closed over, so
2015
+ * the model cannot choose whose memory to read.
2016
+ */
2017
+ as_tool(options = {}) {
2018
+ return buildTool(this, options, (message) => this.#warn(message));
2019
+ }
2020
+ /**
2021
+ * A read-only diagnostic view of the in-process anticipation cache.
2022
+ *
2023
+ * Mirrors Python's `anticipation_cache_snapshot()`. Returns the same
2024
+ * snake_case shape, since this output gets logged and compared across SDKs.
2025
+ */
2026
+ anticipation_cache_snapshot() {
2027
+ return this.#anticipationCache.snapshot();
2028
+ }
2029
+ /** Python logs via `logger.warning`; here it is one overridable seam. */
2030
+ #warn(message) {
2031
+ console.warn(`[synap] ${message}`);
2032
+ }
2033
+ /** Release timers and connections. Safe to call more than once. */
2034
+ async shutdown() {
2035
+ if (this.#closed) return;
2036
+ this.#closed = true;
2037
+ await this.instance.stop_listening();
2038
+ this.#compactionSubscribers.removeAll();
2039
+ this.#transport.close();
2040
+ this.#anticipationCache.clear();
2041
+ }
2042
+ };
2043
+ function pick2(o2, snake, camel) {
2044
+ const a = o2[snake];
2045
+ return a !== void 0 ? a : o2[camel];
2046
+ }
2047
+ function resolveCredentials(options) {
2048
+ const apiKey = options.apiKey ?? getEnv("SYNAP_API_KEY");
2049
+ if (!apiKey) {
2050
+ throw new InvalidInputError(
2051
+ "No Synap API key found. Set SYNAP_API_KEY in your environment, or pass apiKey to the SynapClient constructor."
2052
+ );
2053
+ }
2054
+ return {
2055
+ apiKey,
2056
+ clientId: options.clientId ?? getEnv("SYNAP_CLIENT_ID") ?? "",
2057
+ instanceId: options.instanceId ?? getEnv("SYNAP_INSTANCE_ID") ?? ""
2058
+ };
2059
+ }
2060
+ export {
2061
+ ANY_SCOPE,
2062
+ AgentUnavailableError,
2063
+ AnticipationCache,
2064
+ AuthenticationError,
2065
+ BM25,
2066
+ CONTRACT,
2067
+ ConflictError,
2068
+ ConnectionError,
2069
+ ContextNotFoundError,
2070
+ DEFAULT_BASE_URL,
2071
+ DEFAULT_DOCUMENT_TYPE,
2072
+ DEFAULT_INGEST_MODE,
2073
+ DEFAULT_RETRY_POLICY,
2074
+ DEFAULT_TIMEOUTS,
2075
+ DOCUMENT_TYPES,
2076
+ ENDPOINTS,
2077
+ HttpTransport,
2078
+ INGEST_MODES,
2079
+ ITEM_COLLECTIONS,
2080
+ InsufficientCreditsError,
2081
+ InvalidConversationIdError,
2082
+ InvalidInputError,
2083
+ InvalidInstanceIdError,
2084
+ ListeningAlreadyActiveError,
2085
+ ListeningNotActiveError,
2086
+ NetworkTimeoutError,
2087
+ PermanentError,
2088
+ RateLimitError,
2089
+ SDKError,
2090
+ SDK_VERSION,
2091
+ ServiceUnavailableError,
2092
+ SessionExpiredError,
2093
+ SynapClient,
2094
+ SynapError,
2095
+ SynapPermanentError,
2096
+ SynapTransientError,
2097
+ THRESHOLDS,
2098
+ TranscriptConflictError,
2099
+ TransientError,
2100
+ flattenContextItems,
2101
+ formatForPrompt,
2102
+ isOutcomeUnknown,
2103
+ isRecallQuery,
2104
+ isSynapError,
2105
+ mergeScopeResults,
2106
+ resolvePath,
2107
+ shouldRetry,
2108
+ stem,
2109
+ tokenize
2110
+ };
2111
+ //# sourceMappingURL=index.js.map