@maximem/synap-js-sdk 0.3.0 → 0.4.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 +147 -0
  2. package/README.md +95 -82
  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 -23
  47. package/.env.example +0 -39
  48. package/bridge/synap_bridge.py +0 -768
  49. package/src/bridge-manager.js +0 -262
  50. package/src/errors.js +0 -168
  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 -464
  55. package/types/index.d.ts +0 -475
@@ -0,0 +1,1477 @@
1
+ import { a as AnticipationCacheSnapshot } from './anticipation-cache-BgkqcTPJ.js';
2
+ export { b as ANY_SCOPE, A as AnticipationCache, c as AnticipationCacheOptions, B as BundleItem, C as ContextBundle, E as ExitReason, L as LookupParams, d as LookupResult, e as LookupTelemetry, S as SnapshotBundle, f as SnapshotItemPreview, g as SnapshotItemRecord } from './anticipation-cache-BgkqcTPJ.js';
3
+
4
+ /**
5
+ * AUTOGENERATED FROM synap/sdk/CONTRACT/endpoints.yaml -- DO NOT EDIT.
6
+ * Regenerate with synap/sdk/scripts/sync-contract.sh.
7
+ *
8
+ * The /api/v1 vs /v1 prefix split is intentional and load-bearing. See the
9
+ * contract file: normalising it 404s every affected call in production.
10
+ */
11
+ interface EndpointSpec {
12
+ readonly method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
13
+ readonly path: string;
14
+ /**
15
+ * Whether a retry after an ambiguous failure is safe.
16
+ *
17
+ * False here means a retry can double-ingest and therefore double-bill.
18
+ * See defect D / gotcha G-L in the migration plan.
19
+ */
20
+ readonly idempotent: boolean;
21
+ }
22
+ declare const ENDPOINTS: {
23
+ readonly whoami: {
24
+ readonly method: "GET";
25
+ readonly path: "/api/v1/auth/whoami";
26
+ readonly idempotent: true;
27
+ };
28
+ readonly memories_create: {
29
+ readonly method: "POST";
30
+ readonly path: "/api/v1/memories/create";
31
+ readonly idempotent: false;
32
+ };
33
+ readonly memories_batch: {
34
+ readonly method: "POST";
35
+ readonly path: "/api/v1/memories/batch";
36
+ readonly idempotent: false;
37
+ };
38
+ readonly memories_upload: {
39
+ readonly method: "POST";
40
+ readonly path: "/api/v1/memories/upload";
41
+ readonly idempotent: false;
42
+ };
43
+ readonly memories_status: {
44
+ readonly method: "GET";
45
+ readonly path: "/api/v1/memories/status/{ingestion_id}";
46
+ readonly idempotent: true;
47
+ };
48
+ readonly memories_get: {
49
+ readonly method: "GET";
50
+ readonly path: "/api/v1/memories/{memory_id}";
51
+ readonly idempotent: true;
52
+ };
53
+ readonly memories_update: {
54
+ readonly method: "PATCH";
55
+ readonly path: "/api/v1/memories/{memory_id}";
56
+ readonly idempotent: false;
57
+ };
58
+ readonly memories_delete: {
59
+ readonly method: "DELETE";
60
+ readonly path: "/api/v1/memories/{memory_id}";
61
+ readonly idempotent: true;
62
+ };
63
+ readonly context_fetch_user: {
64
+ readonly method: "POST";
65
+ readonly path: "/v1/context/user/fetch";
66
+ readonly idempotent: true;
67
+ };
68
+ readonly context_fetch_customer: {
69
+ readonly method: "POST";
70
+ readonly path: "/v1/context/customer/fetch";
71
+ readonly idempotent: true;
72
+ };
73
+ readonly context_fetch_client: {
74
+ readonly method: "POST";
75
+ readonly path: "/v1/context/client/fetch";
76
+ readonly idempotent: true;
77
+ };
78
+ readonly context_fetch_conversation: {
79
+ readonly method: "POST";
80
+ readonly path: "/v1/context/conversation/fetch";
81
+ readonly idempotent: true;
82
+ };
83
+ readonly context_for_prompt: {
84
+ readonly method: "GET";
85
+ readonly path: "/v1/conversations/{conversation_id}/context-for-prompt";
86
+ readonly idempotent: true;
87
+ };
88
+ readonly conversations_ingest: {
89
+ readonly method: "POST";
90
+ readonly path: "/v1/conversations/ingest";
91
+ readonly idempotent: false;
92
+ };
93
+ readonly conversations_messages: {
94
+ readonly method: "POST";
95
+ readonly path: "/v1/conversations/messages";
96
+ readonly idempotent: false;
97
+ };
98
+ readonly conversations_messages_batch: {
99
+ readonly method: "POST";
100
+ readonly path: "/v1/conversations/messages/batch";
101
+ readonly idempotent: false;
102
+ };
103
+ readonly conversations_compact: {
104
+ readonly method: "POST";
105
+ readonly path: "/v1/conversations/compact";
106
+ readonly idempotent: false;
107
+ };
108
+ readonly conversations_compacted: {
109
+ readonly method: "GET";
110
+ readonly path: "/v1/conversations/{conversation_id}/compacted";
111
+ readonly idempotent: true;
112
+ };
113
+ readonly conversations_compaction_status: {
114
+ readonly method: "GET";
115
+ readonly path: "/v1/conversations/{conversation_id}/compaction/status";
116
+ readonly idempotent: true;
117
+ };
118
+ readonly user_profile: {
119
+ readonly method: "GET";
120
+ readonly path: "/v1/users/{user_id}/profile";
121
+ readonly idempotent: true;
122
+ };
123
+ readonly credits_balance: {
124
+ readonly method: "GET";
125
+ readonly path: "/v1/credits/balance";
126
+ readonly idempotent: true;
127
+ };
128
+ readonly credits_ledger: {
129
+ readonly method: "GET";
130
+ readonly path: "/v1/credits/ledger";
131
+ readonly idempotent: true;
132
+ };
133
+ readonly credits_estimate: {
134
+ readonly method: "POST";
135
+ readonly path: "/v1/credits/estimate";
136
+ readonly idempotent: true;
137
+ };
138
+ readonly credits_redeem: {
139
+ readonly method: "POST";
140
+ readonly path: "/v1/credits/redeem";
141
+ readonly idempotent: false;
142
+ };
143
+ readonly telemetry_batch: {
144
+ readonly method: "POST";
145
+ readonly path: "/v1/telemetry/batch";
146
+ readonly idempotent: false;
147
+ };
148
+ };
149
+ type EndpointName = keyof typeof ENDPOINTS;
150
+ /** Substitute `{placeholder}` segments. Values are URL-encoded.
151
+ *
152
+ * Encoding matters: an unencoded id containing `/` or `?` silently
153
+ * retargets the request at a different route.
154
+ */
155
+ declare function resolvePath(name: EndpointName, params?: Readonly<Record<string, string | number>>): string;
156
+
157
+ /**
158
+ * Retry policy, mirroring `maximem_synap/resilience/retry.py` plus the
159
+ * idempotency gate added there for defect D.
160
+ *
161
+ * ## Why idempotency is part of the retry decision
162
+ *
163
+ * The obvious retry rule is "retry transient errors". That rule double-bills.
164
+ *
165
+ * `POST /api/v1/memories/create` is metered ingestion. If the request reached
166
+ * the server, was processed, and the *response* was lost, a retry ingests the
167
+ * same content twice and bills for it twice. The client cannot tell that case
168
+ * apart from "the request never arrived" by looking at the error type alone.
169
+ *
170
+ * So retries are gated on two independent questions:
171
+ *
172
+ * 1. Is the error transient? (worth retrying at all)
173
+ * 2. Is the outcome *knowable*? (safe to retry a non-idempotent call)
174
+ *
175
+ * A connection error means the request provably never reached the application,
176
+ * so the outcome is known: nothing happened. A read timeout means the request
177
+ * may well have been processed. That is the ambiguous case, and for a
178
+ * non-idempotent method it is where we stop.
179
+ *
180
+ * The residual window -- server accepted the request, began processing, then
181
+ * died before responding -- cannot be closed from the client at all. Closing it
182
+ * requires a server-honoured idempotency key on the ingest route. Until that
183
+ * exists, this policy narrows the window rather than eliminating it.
184
+ */
185
+ interface RetryPolicy {
186
+ maxAttempts: number;
187
+ /** Base delay in seconds. */
188
+ backoffBase: number;
189
+ /** Delay cap in seconds. */
190
+ backoffMax: number;
191
+ /** Randomise the delay to avoid a thundering herd. */
192
+ backoffJitter: boolean;
193
+ }
194
+ declare const DEFAULT_RETRY_POLICY: RetryPolicy;
195
+ interface RetryDecisionInput {
196
+ error: unknown;
197
+ /** 1-based. */
198
+ attempt: number;
199
+ policy: RetryPolicy;
200
+ method: string;
201
+ /**
202
+ * True when the request may have been processed by the server despite the
203
+ * error. Read timeouts and mid-flight disconnects are ambiguous; connection
204
+ * errors and definitive HTTP status responses are not.
205
+ */
206
+ outcomeUnknown: boolean;
207
+ /** Overrides idempotency for a route, e.g. a read-only POST. */
208
+ idempotent?: boolean;
209
+ }
210
+ interface RetryDecision {
211
+ retry: boolean;
212
+ /** Seconds to wait before the next attempt. */
213
+ delaySeconds: number;
214
+ reason: string;
215
+ }
216
+ declare function shouldRetry(input: RetryDecisionInput): RetryDecision;
217
+
218
+ interface KeepAliveOptions {
219
+ keepAliveExpiryMs?: number;
220
+ maxConnections?: number;
221
+ }
222
+
223
+ /**
224
+ * Native HTTP transport.
225
+ *
226
+ * Port of `maximem_synap/transport/http_client.py`. Uses global `fetch` so the
227
+ * same code runs on Node, Edge, Workers and Bun, with an optional undici
228
+ * dispatcher on Node to restore Python's connection reuse (see keepalive.ts).
229
+ */
230
+
231
+ declare const DEFAULT_BASE_URL = "https://synap-cloud-prod.maximem.ai";
232
+ interface TimeoutConfig {
233
+ /** Seconds. Mirrors TimeoutConfig in the Python SDK. */
234
+ connect: number;
235
+ read: number;
236
+ write: number;
237
+ }
238
+ declare const DEFAULT_TIMEOUTS: TimeoutConfig;
239
+ interface Credentials {
240
+ apiKey: string;
241
+ clientId: string;
242
+ instanceId: string;
243
+ }
244
+ interface HttpTransportOptions {
245
+ credentials: Credentials;
246
+ baseUrl?: string;
247
+ timeouts?: Partial<TimeoutConfig>;
248
+ retryPolicy?: Partial<RetryPolicy>;
249
+ keepAlive?: KeepAliveOptions & {
250
+ heartbeatIntervalMs?: number;
251
+ };
252
+ userAgent?: string;
253
+ /** Injected in tests. Defaults to global fetch. */
254
+ fetchImpl?: typeof fetch;
255
+ }
256
+ interface RequestOptions {
257
+ pathParams?: Record<string, string | number>;
258
+ query?: Record<string, string | number | boolean | undefined>;
259
+ body?: unknown;
260
+ /**
261
+ * Send `body` as-is instead of JSON-encoding it, and drop the JSON
262
+ * Content-Type so the runtime can set `multipart/form-data` with its own
263
+ * boundary. Setting Content-Type by hand for FormData produces a body the
264
+ * server cannot parse, because the boundary will not match.
265
+ */
266
+ rawBody?: boolean;
267
+ correlationId?: string;
268
+ /** Overrides the endpoint's declared idempotency. */
269
+ idempotent?: boolean;
270
+ signal?: AbortSignal;
271
+ }
272
+ declare class HttpTransport {
273
+ #private;
274
+ readonly baseUrl: string;
275
+ timeouts: TimeoutConfig;
276
+ retryPolicy: RetryPolicy;
277
+ private readonly userAgent;
278
+ private readonly fetchImpl;
279
+ private readonly keepAliveOptions;
280
+ private readonly heartbeat;
281
+ private closed;
282
+ constructor(options: HttpTransportOptions);
283
+ /** Start keeping the connection warm. Opt-in: it is pointless in serverless. */
284
+ startHeartbeat(): void;
285
+ /**
286
+ * Release timers. Safe to call more than once.
287
+ *
288
+ * This is the supported shutdown path, deliberately explicit rather than
289
+ * hooked to `process.on('exit')`: those handlers leak a listener per client,
290
+ * never fire in Lambda, and `process` does not exist in Workers (gotcha G-I).
291
+ */
292
+ /**
293
+ * A COPY of the current credentials.
294
+ *
295
+ * A copy, not the live object: handing out the internal reference is how
296
+ * `client.transport.credentials.apiKey` became readable and mutable from
297
+ * outside in the first place.
298
+ */
299
+ currentCredentials(): Credentials;
300
+ /** Write back identity resolved from `GET /api/v1/auth/whoami`. */
301
+ updateCredentials(patch: Partial<Credentials>): void;
302
+ setTimeouts(timeouts: Partial<TimeoutConfig>): void;
303
+ /** `null` restores the default policy, matching Python's `retry_policy=None`. */
304
+ setRetryPolicy(policy: Partial<RetryPolicy> | null): void;
305
+ close(): void;
306
+ request<T = unknown>(name: EndpointName, options?: RequestOptions): Promise<T>;
307
+ private attempt;
308
+ private buildUrl;
309
+ private buildHeaders;
310
+ /** Unauthenticated and unmetered, exactly like the Python heartbeat. */
311
+ private pingHealth;
312
+ }
313
+ /**
314
+ * Whether the server may have processed the request despite this error.
315
+ *
316
+ * This is the input to the double-billing gate in retry.ts, so the bias is
317
+ * deliberate: anything we cannot positively rule out counts as ambiguous.
318
+ */
319
+ declare function isOutcomeUnknown(error: unknown): boolean;
320
+
321
+ /**
322
+ * Context shapes.
323
+ *
324
+ * There are deliberately TWO of these, and they are both live in production:
325
+ *
326
+ * - **Raw snake_case** (`RawContext`), returned by the namespaced surface
327
+ * (`client.user.context.fetch(...)`), which passes `result.context` through
328
+ * untouched.
329
+ * - **Normalised camelCase** (`NormalisedContext`), returned by the legacy
330
+ * top-level methods (`client.fetchUserContext(...)`).
331
+ *
332
+ * Converging them would be a clean improvement and would also break one of the
333
+ * two sets of existing consumers, so it is a 2.0 change, not a port change.
334
+ * See gotcha G-B in the migration plan. Do not "tidy" this.
335
+ */
336
+ type Json = Record<string, unknown>;
337
+ interface RawContextItem {
338
+ id?: string;
339
+ item_id?: string;
340
+ content?: string;
341
+ confidence?: number;
342
+ source?: string;
343
+ extracted_at?: string | null;
344
+ metadata?: Json;
345
+ event_date?: string | null;
346
+ valid_until?: string | null;
347
+ temporal_category?: string | null;
348
+ temporal_confidence?: number;
349
+ [key: string]: unknown;
350
+ }
351
+ interface RawConversationContext {
352
+ summary?: string | null;
353
+ current_state?: Json;
354
+ current_state_json?: string;
355
+ key_extractions?: Json;
356
+ key_extractions_json?: string;
357
+ recent_turns?: unknown[];
358
+ compaction_id?: string | null;
359
+ compacted_at?: string | null;
360
+ conversation_id?: string | null;
361
+ [key: string]: unknown;
362
+ }
363
+ interface RawContext {
364
+ facts?: RawContextItem[];
365
+ preferences?: RawContextItem[];
366
+ episodes?: RawContextItem[];
367
+ emotions?: RawContextItem[];
368
+ temporal_events?: RawContextItem[];
369
+ conversation_context?: RawConversationContext | null;
370
+ metadata?: Json;
371
+ [key: string]: unknown;
372
+ }
373
+ interface TemporalFields {
374
+ eventDate: string | null;
375
+ validUntil: string | null;
376
+ temporalCategory: string | null;
377
+ temporalConfidence: number;
378
+ }
379
+ interface Fact extends TemporalFields {
380
+ id: string;
381
+ content: string;
382
+ confidence: number;
383
+ source: string;
384
+ extractedAt: string | null;
385
+ metadata: Json;
386
+ }
387
+ interface Preference extends TemporalFields {
388
+ id: string;
389
+ category: string;
390
+ content: string;
391
+ strength: number;
392
+ source: string;
393
+ extractedAt: string | null;
394
+ metadata: Json;
395
+ }
396
+ interface Episode extends TemporalFields {
397
+ id: string;
398
+ summary: string;
399
+ occurredAt: string | null;
400
+ significance: number;
401
+ participants: unknown[];
402
+ metadata: Json;
403
+ }
404
+ interface Emotion extends TemporalFields {
405
+ id: string;
406
+ emotionType: string;
407
+ intensity: number;
408
+ detectedAt: string | null;
409
+ context: string;
410
+ metadata: Json;
411
+ }
412
+ interface TemporalEvent {
413
+ id: string;
414
+ content: string;
415
+ eventDate: string | null;
416
+ validUntil: string | null;
417
+ /**
418
+ * Nullable despite the `''` default in the normaliser: `pickDefined` skips
419
+ * only `undefined`, so an explicit server `null` is preserved and the
420
+ * default never applies. The declared type follows the runtime, not the
421
+ * default.
422
+ */
423
+ temporalCategory: string | null;
424
+ temporalConfidence: number;
425
+ confidence: number;
426
+ source: string;
427
+ extractedAt: string | null;
428
+ metadata: Json;
429
+ }
430
+ interface ConversationContext {
431
+ summary: string | null;
432
+ currentState: Json;
433
+ keyExtractions: Json;
434
+ recentTurns: unknown[];
435
+ compactionId: string | null;
436
+ compactedAt: string | null;
437
+ conversationId: string | null;
438
+ }
439
+ interface ContextMetadata {
440
+ correlationId: string;
441
+ ttlSeconds: number;
442
+ source: string;
443
+ retrievedAt: string | null;
444
+ compactionApplied: boolean | null;
445
+ }
446
+ interface NormalisedContext {
447
+ facts: Fact[];
448
+ preferences: Preference[];
449
+ episodes: Episode[];
450
+ emotions: Emotion[];
451
+ temporalEvents: TemporalEvent[];
452
+ conversationContext: ConversationContext | null;
453
+ metadata: ContextMetadata;
454
+ rawResponse: Json;
455
+ }
456
+ /**
457
+ * Fetch options.
458
+ *
459
+ * Both spellings of every id are accepted. The removed wrapper did this
460
+ * (`pickDefined(args.user_id, args.userId)`) and callers depend on it, so
461
+ * rejecting one spelling would be a silent breaking change.
462
+ */
463
+ interface FetchOptions {
464
+ user_id?: string;
465
+ userId?: string;
466
+ customer_id?: string;
467
+ customerId?: string;
468
+ conversation_id?: string;
469
+ conversationId?: string;
470
+ search_query?: string[];
471
+ searchQuery?: string[];
472
+ max_results?: number;
473
+ maxResults?: number;
474
+ types?: string[];
475
+ mode?: string;
476
+ precision_level?: string;
477
+ precisionLevel?: string;
478
+ /** Sent only when explicitly false, matching the Python controllers. */
479
+ include_conversation_context?: boolean;
480
+ }
481
+
482
+ /**
483
+ * Flatten typed context collections into one memory list.
484
+ *
485
+ * A faithful port of `flatten_context_items` from the 0.3.x bridge, which is
486
+ * what `searchMemory` and `getMemories` returned. The per-type asymmetries are
487
+ * deliberate and are reproduced exactly:
488
+ *
489
+ * - `memory` comes from a different field per type: fact/preference/temporal
490
+ * event use `content`, episode uses `summary`, emotion uses `context`.
491
+ * - `score` likewise: fact -> confidence, preference -> strength,
492
+ * episode -> significance, emotion -> intensity, temporal event ->
493
+ * temporal_confidence.
494
+ * - **episode and emotion carry no `source` key at all.** The bridge simply
495
+ * did not set one. Adding it would be an improvement and a shape change, so
496
+ * it stays absent.
497
+ * - Order is facts, preferences, episodes, emotions, temporal events.
498
+ */
499
+
500
+ interface FlatMemory {
501
+ id: string;
502
+ memory: string;
503
+ score: number;
504
+ /** Absent for episodes and emotions, matching the 0.3.x bridge. */
505
+ source?: string;
506
+ metadata: Json;
507
+ context_type: 'fact' | 'preference' | 'episode' | 'emotion' | 'temporal_event';
508
+ event_date: string | null;
509
+ valid_until: string | null;
510
+ temporal_category: string | null;
511
+ temporal_confidence: number;
512
+ }
513
+ declare function flattenContextItems(context: RawContext): FlatMemory[];
514
+
515
+ /**
516
+ * Memories namespace. Mirrors `maximem_synap.memories.interface.MemoriesInterface`.
517
+ *
518
+ * ## The request body is built the way Python builds it, on purpose
519
+ *
520
+ * Python constructs a pydantic `CreateMemoryRequest` and posts
521
+ * `model_dump(mode="json")`, which emits **every** field, nulls included, with
522
+ * defaults applied. It is tempting to omit unset keys instead and send a
523
+ * tidier body. Do not: the server sees a different request, and the difference
524
+ * that matters most is `document_type`.
525
+ *
526
+ * `document_type` defaults to `"ai-chat-conversation"`. Leaving it off is not
527
+ * neutral, because the extraction path is type-dependent and the
528
+ * `"document"` type extracts dramatically less from the same text. A body that
529
+ * merely looks cleaner is how you get "the JS SDK stores fewer memories than
530
+ * the Python one from identical input", which is slow and miserable to
531
+ * diagnose.
532
+ */
533
+
534
+ /** Mirrors the Python DocumentType enum. */
535
+ declare const DOCUMENT_TYPES: readonly ["ai-chat-conversation", "document", "email", "pdf", "image", "audio", "meeting-transcript"];
536
+ type DocumentType = (typeof DOCUMENT_TYPES)[number];
537
+ /** Mirrors the Python IngestMode enum. */
538
+ declare const INGEST_MODES: readonly ["fast", "long-range"];
539
+ type IngestMode = (typeof INGEST_MODES)[number];
540
+ declare const DEFAULT_DOCUMENT_TYPE: DocumentType;
541
+ declare const DEFAULT_INGEST_MODE: IngestMode;
542
+ interface CreateMemoryOptions {
543
+ document: string;
544
+ user_id?: string | null;
545
+ userId?: string | null;
546
+ customer_id?: string | null;
547
+ customerId?: string | null;
548
+ document_type?: DocumentType | string;
549
+ documentType?: DocumentType | string;
550
+ document_id?: string | null;
551
+ documentId?: string | null;
552
+ /** ISO-8601 string or Date. Serialised the way Python serialises a datetime. */
553
+ document_created_at?: string | Date | null;
554
+ documentCreatedAt?: string | Date | null;
555
+ mode?: IngestMode | string;
556
+ metadata?: Json;
557
+ }
558
+ interface CreateMemoryResult {
559
+ ingestion_id?: string;
560
+ status?: string;
561
+ memories_created?: number;
562
+ [key: string]: unknown;
563
+ }
564
+ interface BatchCreateOptions {
565
+ documents: CreateMemoryOptions[];
566
+ fail_fast?: boolean;
567
+ failFast?: boolean;
568
+ }
569
+ interface MemoriesInterface {
570
+ create(options: CreateMemoryOptions): Promise<CreateMemoryResult>;
571
+ batch_create(options: BatchCreateOptions): Promise<Json>;
572
+ create_from_file(options: CreateFromFileOptions): Promise<CreateMemoryResult>;
573
+ get(memoryId: string): Promise<Json>;
574
+ update(options: UpdateMemoryOptions): Promise<Json>;
575
+ delete(memoryId: string): Promise<Json>;
576
+ status(ingestionId: string): Promise<Json>;
577
+ wait_for_completion(ingestionId: string, options?: WaitOptions): Promise<Json>;
578
+ }
579
+ /**
580
+ * Options for `create_from_file`.
581
+ *
582
+ * Exactly one of `file_path`, `file`, or `text` must be supplied, matching
583
+ * Python. `file_path` reads from disk and so is Node-only; `file` takes a
584
+ * Blob/File/Uint8Array and works anywhere, which is the portable route for
585
+ * Edge and Workers.
586
+ */
587
+ interface CreateFromFileOptions {
588
+ user_id?: string;
589
+ userId?: string;
590
+ customer_id?: string;
591
+ customerId?: string;
592
+ /** "b2b" or "b2c". Defaults to "b2c", as in Python. */
593
+ relationship_type?: string;
594
+ relationshipType?: string;
595
+ /** Node-only: read this path from disk. Uses a lazy `node:fs` import. */
596
+ file_path?: string;
597
+ filePath?: string;
598
+ /** Portable alternative to `file_path`. Pass `filename` alongside a raw buffer. */
599
+ file?: Blob | Uint8Array | ArrayBuffer;
600
+ filename?: string;
601
+ /** Ingest raw text instead of a file. */
602
+ text?: string;
603
+ document_type?: string;
604
+ documentType?: string;
605
+ mode?: IngestMode | string;
606
+ metadata?: Json;
607
+ }
608
+ interface UpdateMemoryOptions {
609
+ memory_id?: string;
610
+ memoryId?: string;
611
+ document: string;
612
+ merge_strategy?: string;
613
+ mergeStrategy?: string;
614
+ document_type?: string;
615
+ documentType?: string;
616
+ metadata?: Json;
617
+ }
618
+ interface WaitOptions {
619
+ /** Seconds. Matches Python's default. */
620
+ timeout_seconds?: number;
621
+ timeoutSeconds?: number;
622
+ poll_interval_seconds?: number;
623
+ pollIntervalSeconds?: number;
624
+ signal?: AbortSignal;
625
+ }
626
+
627
+ /**
628
+ * Conversation namespace. Mirrors `ConversationInterface` and
629
+ * `ConversationContextInterface` in the Python SDK.
630
+ */
631
+
632
+ interface RecordMessageOptions {
633
+ conversation_id?: string;
634
+ conversationId?: string;
635
+ role: string;
636
+ content: string;
637
+ user_id?: string;
638
+ userId?: string;
639
+ customer_id?: string;
640
+ customerId?: string;
641
+ session_id?: string;
642
+ sessionId?: string;
643
+ metadata?: Json;
644
+ }
645
+ interface TranscriptTurn {
646
+ role: string;
647
+ content: string;
648
+ [key: string]: unknown;
649
+ }
650
+ interface IngestTranscriptOptions {
651
+ conversation_id?: string;
652
+ conversationId?: string;
653
+ user_id?: string;
654
+ userId?: string;
655
+ transcript: string | TranscriptTurn[];
656
+ customer_id?: string;
657
+ customerId?: string;
658
+ conversation_type?: string;
659
+ conversationType?: string;
660
+ analysis?: Json;
661
+ metadata?: Json;
662
+ started_at?: string | Date;
663
+ startedAt?: string | Date;
664
+ ended_at?: string | Date;
665
+ endedAt?: string | Date;
666
+ }
667
+ interface CompactOptions {
668
+ conversation_id?: string;
669
+ conversationId?: string;
670
+ strategy?: string;
671
+ compaction_level?: string;
672
+ compactionLevel?: string;
673
+ target_tokens?: number;
674
+ targetTokens?: number;
675
+ force?: boolean;
676
+ }
677
+ interface ConversationNamespace {
678
+ record_message(options: RecordMessageOptions): Promise<Json>;
679
+ record_messages_batch(messages: RecordMessageOptions[]): Promise<Json>;
680
+ ingest_transcript(options: IngestTranscriptOptions): Promise<Json>;
681
+ context: {
682
+ fetch(options?: FetchOptions): Promise<RawContext>;
683
+ get_context_for_prompt(options?: {
684
+ conversation_id?: string;
685
+ conversationId?: string;
686
+ style?: string;
687
+ }): Promise<Json>;
688
+ compact(options: CompactOptions): Promise<Json>;
689
+ get_compacted(options: {
690
+ conversation_id?: string;
691
+ conversationId?: string;
692
+ version?: number;
693
+ format?: string;
694
+ }): Promise<Json | null>;
695
+ get_compaction_status(options: {
696
+ conversation_id?: string;
697
+ conversationId?: string;
698
+ } | string): Promise<Json>;
699
+ subscribe_to_compaction_updates(conversationId: string, callback: CompactionCallback): () => void;
700
+ unsubscribe_all_compaction_updates(conversationId?: string): number;
701
+ };
702
+ }
703
+ type CompactionCallback = (bundle: Json) => unknown;
704
+ /**
705
+ * Called after a successful write with the ids that were written for.
706
+ *
707
+ * Backs `SYNAP_SDK_CACHE_INVALIDATE_ON_WRITE`. The cloud invalidates its own
708
+ * Redis caches on ingest, but that stops at the process boundary: without this
709
+ * the in-SDK anticipation cache keeps serving the pre-write view for the rest
710
+ * of its TTL. Python calls its equivalent from these same three write paths.
711
+ */
712
+ type WriteInvalidator = (ids: {
713
+ userId?: string | undefined;
714
+ customerId?: string | undefined;
715
+ }) => void;
716
+
717
+ /** User namespace. Mirrors `UserInterface` in the Python SDK. */
718
+
719
+ interface UserNamespace {
720
+ get_profile(options: {
721
+ user_id?: string;
722
+ userId?: string;
723
+ customer_id?: string;
724
+ customerId?: string;
725
+ } | string): Promise<Json>;
726
+ context: {
727
+ fetch(options?: FetchOptions): Promise<RawContext>;
728
+ };
729
+ }
730
+
731
+ /** Credits namespace. Mirrors `CreditsInterface` in the Python SDK. */
732
+
733
+ interface EstimateOptions {
734
+ metric_type?: string;
735
+ metricType?: string;
736
+ units?: number;
737
+ item_count?: number;
738
+ itemCount?: number;
739
+ endpoint?: string;
740
+ mode?: string;
741
+ }
742
+ interface CreditsNamespace {
743
+ get_balance(): Promise<Json>;
744
+ get_ledger(options?: {
745
+ limit?: number;
746
+ offset?: number;
747
+ }): Promise<Json>;
748
+ estimate(options: EstimateOptions): Promise<Json>;
749
+ redeem(code: string): Promise<Json>;
750
+ }
751
+
752
+ /**
753
+ * Cache namespace. Mirrors `CacheInterface` in the Python SDK.
754
+ *
755
+ * This is a deliberately narrow facade over the AnticipationCache rather than
756
+ * the cache object itself. Exposing the instance directly leaked internals
757
+ * (`client.cache.now` was reachable) and gave JS a different surface from
758
+ * Python for no benefit. The cache is still reachable as
759
+ * `client.anticipationCache` for advanced use.
760
+ */
761
+
762
+ interface CacheStats {
763
+ bundles: number;
764
+ items: number;
765
+ [key: string]: unknown;
766
+ }
767
+ interface CacheNamespace {
768
+ clear(): void;
769
+ clear_user(userId: string): void;
770
+ clear_customer(customerId: string): void;
771
+ stats(): CacheStats;
772
+ }
773
+
774
+ /**
775
+ * Instance namespace. Mirrors `InstanceInterface` in the Python SDK.
776
+ *
777
+ * Everything here rides the bidirectional gRPC Listen stream. The stream module
778
+ * is loaded through a lazy `import()` so that importing this SDK on Edge or in
779
+ * a Worker stays safe: grpc-js is built on `node:http2`/`node:net`/`node:tls`
780
+ * and a static import fails the BUILD there, not the call.
781
+ */
782
+
783
+ interface ListenOptions {
784
+ /** Called with the attempt count each time the stream reconnects. */
785
+ on_reconnect?: (attempt: number) => void;
786
+ onReconnect?: (attempt: number) => void;
787
+ /** Called with a reason string when the stream drops. */
788
+ on_disconnect?: (reason: string) => void;
789
+ onDisconnect?: (reason: string) => void;
790
+ /**
791
+ * Called for every bundle that arrives. Bundles are ALSO stored in the
792
+ * anticipation cache automatically, so `fetch()` finds them without a round
793
+ * trip; this callback is for observing, not for wiring up storage.
794
+ */
795
+ on_context?: (bundle: Record<string, unknown>) => void;
796
+ onContext?: (bundle: Record<string, unknown>) => void;
797
+ /** Overrides `SYNAP_GRPC_HOST`. */
798
+ host?: string;
799
+ /** Overrides `SYNAP_GRPC_PORT`. */
800
+ port?: number;
801
+ /** Overrides `SYNAP_GRPC_USE_TLS`. Plaintext only makes sense against localhost. */
802
+ use_tls?: boolean;
803
+ useTls?: boolean;
804
+ }
805
+ interface SendMessageOptions {
806
+ content: string;
807
+ role?: string;
808
+ conversation_id?: string;
809
+ conversationId?: string;
810
+ user_id?: string;
811
+ userId?: string;
812
+ customer_id?: string;
813
+ customerId?: string;
814
+ session_id?: string;
815
+ sessionId?: string;
816
+ event_type?: string;
817
+ eventType?: string;
818
+ metadata?: Record<string, string>;
819
+ search_queries?: string[];
820
+ searchQueries?: string[];
821
+ context_types?: string[];
822
+ contextTypes?: string[];
823
+ }
824
+ interface RecordThinkingOptions {
825
+ content: string;
826
+ conversation_id?: string;
827
+ conversationId?: string;
828
+ user_id?: string;
829
+ userId?: string;
830
+ customer_id?: string;
831
+ customerId?: string;
832
+ session_id?: string;
833
+ sessionId?: string;
834
+ metadata?: Record<string, string>;
835
+ }
836
+ interface InstanceNamespace {
837
+ listen(options?: ListenOptions): Promise<void>;
838
+ stop_listening(): Promise<void>;
839
+ send_message(options: SendMessageOptions): Promise<void>;
840
+ record_thinking(options: RecordThinkingOptions): Promise<void>;
841
+ /** Property, not a method, matching Python's InstanceInterface.is_listening. */
842
+ readonly is_listening: boolean;
843
+ /** Not in Python: lets the compaction subscriptions share this one stream. */
844
+ readonly _internal: InstanceInternals;
845
+ }
846
+ /** @internal Shared with the conversation namespace so both use one stream. */
847
+ interface InstanceInternals {
848
+ onCompactionUpdate(conversationId: string, bundle: Record<string, unknown>): void;
849
+ setCompactionDispatcher(dispatch: (conversationId: string, bundle: Record<string, unknown>) => void): void;
850
+ isListening(): boolean;
851
+ }
852
+
853
+ /**
854
+ * Cross-scope merge + prompt formatting for `client.fetch()`.
855
+ *
856
+ * Ports Python's `UnifiedContextResponse.merge` and `format_for_prompt` from
857
+ * `models/context.py`. The formatted string is the part customers actually
858
+ * paste into a system prompt, so it is reproduced character for character:
859
+ * section order, the `- ` bullet, the `[scope: x]` / `strength: 0.8` bracket
860
+ * suffixes, the `## User Context` wrapper, and the blank-line joins. A
861
+ * cosmetic drift here shows up as a diff in every prompt a customer builds.
862
+ */
863
+
864
+ /** The five item collections, in Python's iteration order. */
865
+ declare const ITEM_COLLECTIONS: readonly ["facts", "preferences", "episodes", "emotions", "temporal_events"];
866
+ type ItemCollection = (typeof ITEM_COLLECTIONS)[number];
867
+ interface UnifiedContext {
868
+ facts: RawContextItem[];
869
+ preferences: RawContextItem[];
870
+ episodes: RawContextItem[];
871
+ emotions: RawContextItem[];
872
+ temporal_events: RawContextItem[];
873
+ /** item id -> the scope that supplied it. */
874
+ scope_map: Record<string, string>;
875
+ conversation_context: Json | null;
876
+ profile: Json | null;
877
+ conversations: Json[] | null;
878
+ formatted_context: string;
879
+ scopes_queried: string[];
880
+ total_items: number;
881
+ metadata: Json | null;
882
+ }
883
+ /**
884
+ * Merge per-scope responses, deduplicating by item id.
885
+ *
886
+ * First scope wins on a duplicate, so the caller's scope order is significant.
887
+ * An item with no usable id is kept rather than dropped: Python indexes
888
+ * `scope_map` by `item.id`, and a missing id there would collide every such
889
+ * item onto one key, so keeping-and-not-mapping is the closest faithful
890
+ * behaviour.
891
+ */
892
+ declare function mergeScopeResults(scopeResults: ReadonlyArray<readonly [string, RawContext]>): UnifiedContext;
893
+ interface FormatOptions {
894
+ includeScope?: boolean;
895
+ includeConversationContext?: boolean;
896
+ }
897
+ /**
898
+ * Render the merged context for prompt injection.
899
+ *
900
+ * Mirrors `UnifiedContextResponse.format_for_prompt`. Note the two different
901
+ * bracket styles: facts and episodes only ever carry `[scope: x]` appended
902
+ * directly, while preferences, emotions and temporal events build a joined
903
+ * extras list. That asymmetry is in Python and is preserved.
904
+ */
905
+ declare function formatForPrompt(ctx: UnifiedContext, options?: FormatOptions): string;
906
+
907
+ /**
908
+ * `client.as_tool(...)`: an LLM-ready tool definition for fetching context.
909
+ *
910
+ * Ports Python's `MaximemSynapSDK.as_tool` plus its three module helpers.
911
+ *
912
+ * The point of the helper is that the scope identifiers are CLOSED OVER rather
913
+ * than exposed in the JSON schema. The LLM chooses the query and the limits; it
914
+ * cannot choose whose memory to read. Handing the model a `user_id` parameter
915
+ * is how the per-user privacy filter gets dropped by accident, so the schema
916
+ * deliberately has no such field.
917
+ */
918
+
919
+ type ToolScope = 'conversation' | 'user' | 'customer' | 'client' | 'unified';
920
+ type ToolStyle = 'openai' | 'anthropic';
921
+ interface AsToolOptions {
922
+ scope?: string;
923
+ user_id?: string;
924
+ userId?: string;
925
+ customer_id?: string;
926
+ customerId?: string;
927
+ conversation_id?: string;
928
+ conversationId?: string;
929
+ name?: string;
930
+ description?: string;
931
+ style?: string;
932
+ }
933
+ type ToolHandler = (callArgs?: Json) => Promise<Json>;
934
+ interface OpenAiToolDefinition {
935
+ type: 'function';
936
+ function: {
937
+ name: string;
938
+ description: string;
939
+ parameters: Json;
940
+ };
941
+ handler: ToolHandler;
942
+ }
943
+ interface AnthropicToolDefinition {
944
+ name: string;
945
+ description: string;
946
+ input_schema: Json;
947
+ handler: ToolHandler;
948
+ }
949
+ type ToolDefinition = OpenAiToolDefinition | AnthropicToolDefinition;
950
+
951
+ /**
952
+ * SynapClient: the public entry point.
953
+ *
954
+ * Exposes two surfaces, both live in production:
955
+ *
956
+ * - **Namespaced, snake_case** (`client.user.context.fetch(...)`) is the
957
+ * canonical surface and mirrors the Python SDK method for method. It
958
+ * returns raw snake_case.
959
+ * - **Top-level camelCase** (`client.fetchUserContext(...)`) is deprecated but
960
+ * fully supported. It returns the normalised camelCase shape, and it exists
961
+ * because the 0.3.x wrapper had it.
962
+ *
963
+ * They return different shapes on purpose. See gotcha G-B.
964
+ */
965
+
966
+ interface SynapClientOptions {
967
+ apiKey?: string;
968
+ clientId?: string;
969
+ instanceId?: string;
970
+ baseUrl?: string;
971
+ timeouts?: HttpTransportOptions['timeouts'];
972
+ retryPolicy?: HttpTransportOptions['retryPolicy'];
973
+ keepAlive?: HttpTransportOptions['keepAlive'];
974
+ /**
975
+ * Keep the connection warm with a periodic /health ping.
976
+ *
977
+ * Off by default. A clear win for a long-lived process, pure waste in
978
+ * serverless where the container is frozen between requests anyway.
979
+ */
980
+ heartbeat?: boolean;
981
+ fetchImpl?: typeof fetch;
982
+ }
983
+ interface ScopedContext {
984
+ context: {
985
+ fetch: (options?: FetchOptions) => Promise<RawContext>;
986
+ };
987
+ }
988
+ /**
989
+ * Options for `configure()`.
990
+ *
991
+ * The Python-only keys are declared so a config object can be shared between
992
+ * the two SDKs without a type error. They are accepted and ignored: there is
993
+ * no SQLite cache backend and no global logger to replace here.
994
+ */
995
+ interface ConfigureOptions {
996
+ timeouts?: HttpTransportOptions['timeouts'];
997
+ retryPolicy?: HttpTransportOptions['retryPolicy'] | null;
998
+ retry_policy?: HttpTransportOptions['retryPolicy'] | null;
999
+ /** Accepted and ignored. Python-only. */
1000
+ storage_path?: string;
1001
+ /** Accepted and ignored. Python-only. */
1002
+ cache_backend?: string | null;
1003
+ /** Accepted and ignored. Python-only. */
1004
+ session_timeout_minutes?: number;
1005
+ /** Accepted and ignored. Python-only. */
1006
+ log_level?: string;
1007
+ /** Accepted and ignored. Python-only. */
1008
+ logger?: unknown;
1009
+ }
1010
+ /** Options for the cross-scope `fetch()`. Snake_case, matching Python's kwargs. */
1011
+ interface UnifiedFetchOptions {
1012
+ conversation_id?: string;
1013
+ user_id?: string;
1014
+ customer_id?: string;
1015
+ search_query?: string[];
1016
+ max_results?: number;
1017
+ types?: string[];
1018
+ mode?: string;
1019
+ precision_level?: string;
1020
+ include_conversation_context?: boolean;
1021
+ scopes?: string[];
1022
+ include_scope_labels?: boolean;
1023
+ context_mode?: string;
1024
+ include_profile?: boolean;
1025
+ last_n_conversations?: number;
1026
+ }
1027
+ declare class SynapClient {
1028
+ #private;
1029
+ readonly user: UserNamespace;
1030
+ readonly customer: ScopedContext;
1031
+ readonly client: ScopedContext;
1032
+ readonly conversation: ConversationNamespace;
1033
+ readonly memories: MemoriesInterface;
1034
+ readonly credits: CreditsNamespace;
1035
+ readonly cache: CacheNamespace;
1036
+ readonly instance: InstanceNamespace;
1037
+ constructor(options?: SynapClientOptions);
1038
+ /** @deprecated Use `client.user.context.fetch()`. Note it returns raw snake_case. */
1039
+ fetchUserContext(options?: FetchOptions): Promise<NormalisedContext>;
1040
+ /** @deprecated Use `client.customer.context.fetch()`. */
1041
+ fetchCustomerContext(options?: FetchOptions): Promise<NormalisedContext>;
1042
+ /** @deprecated Use `client.client.context.fetch()`. */
1043
+ fetchClientContext(options?: FetchOptions): Promise<NormalisedContext>;
1044
+ /** @deprecated Use `client.conversation.context.get_context_for_prompt()`. */
1045
+ getContextForPrompt(options?: {
1046
+ conversationId?: string;
1047
+ conversation_id?: string;
1048
+ style?: string;
1049
+ }): Promise<Json>;
1050
+ /**
1051
+ * @deprecated Use `client.user.context.fetch()` and read the typed collections.
1052
+ *
1053
+ * Flattens every context type into one list, exactly as the 0.3.x bridge did.
1054
+ */
1055
+ searchMemory(options: {
1056
+ userId?: string;
1057
+ user_id?: string;
1058
+ customerId?: string;
1059
+ customer_id?: string;
1060
+ query: string;
1061
+ maxResults?: number;
1062
+ max_results?: number;
1063
+ mode?: string;
1064
+ conversationId?: string;
1065
+ conversation_id?: string;
1066
+ types?: string[];
1067
+ }): Promise<{
1068
+ results: FlatMemory[];
1069
+ count: number;
1070
+ }>;
1071
+ /**
1072
+ * @deprecated Use `client.user.context.fetch()` with an empty `search_query`.
1073
+ *
1074
+ * Note the default of 100, not 10: it matches the 0.3.x bridge.
1075
+ */
1076
+ getMemories(options: {
1077
+ userId?: string;
1078
+ user_id?: string;
1079
+ customerId?: string;
1080
+ customer_id?: string;
1081
+ mode?: string;
1082
+ conversationId?: string;
1083
+ conversation_id?: string;
1084
+ maxResults?: number;
1085
+ max_results?: number;
1086
+ types?: string[];
1087
+ }): Promise<{
1088
+ results: FlatMemory[];
1089
+ count: number;
1090
+ }>;
1091
+ /** @deprecated Use `client.memories.create()`. */
1092
+ addMemory(options: CreateMemoryOptions): Promise<CreateMemoryResult>;
1093
+ /**
1094
+ * @deprecated Use `client.memories.delete(memoryId)`.
1095
+ *
1096
+ * The `{ userId }` form is gone: it relied on a process-local id list and so
1097
+ * deleted nothing in serverless while reporting success (gotcha G-F).
1098
+ */
1099
+ deleteMemory(options: {
1100
+ memoryId?: string;
1101
+ memory_id?: string;
1102
+ }): Promise<Json>;
1103
+ /**
1104
+ * Resolve `client_id` and `instance_id` from the API key.
1105
+ *
1106
+ * Mirrors Python's `initialize()`. Calling it is optional here (unlike
1107
+ * Python, which raises "SDK not initialized" from `_ensure_initialized`)
1108
+ * because every HTTP route authenticates on the API key alone. It matters
1109
+ * for the anticipation cache: a bundle shared at client scope is filtered on
1110
+ * `client_id`, and an empty one fails that filter for every bundle. That was
1111
+ * a silent cache-scope bug in Python before whoami bootstrapping existed.
1112
+ *
1113
+ * Best-effort and idempotent. A whoami failure leaves whatever the
1114
+ * constructor resolved and is not fatal.
1115
+ */
1116
+ initialize(): Promise<void>;
1117
+ /**
1118
+ * @deprecated Use `initialize()`, which is the Python name. `init()` was a
1119
+ * JS-only spelling with no Python counterpart; it now forwards, so a 0.3.x
1120
+ * startup script keeps working.
1121
+ */
1122
+ init(): Promise<void>;
1123
+ /** The resolved instance id. Empty until `initialize()` resolves it. */
1124
+ get instance_id(): string;
1125
+ /** The resolved client id. Empty until `initialize()` resolves it. */
1126
+ get client_id(): string;
1127
+ /**
1128
+ * Update configuration before `initialize()`.
1129
+ *
1130
+ * Mirrors Python's `configure()`, including its refusal to reconfigure after
1131
+ * initialization: the transport and cache are already built by then and a
1132
+ * half-applied config is worse than a rejected one.
1133
+ *
1134
+ * The Python-only keys (`storage_path`, `cache_backend`,
1135
+ * `session_timeout_minutes`, `logger`) are accepted and ignored rather than
1136
+ * rejected, because this SDK has no SQLite cache backend and no global
1137
+ * logger to swap. Ignoring them keeps a shared config object portable
1138
+ * between the two SDKs.
1139
+ */
1140
+ configure(options?: ConfigureOptions): void;
1141
+ /**
1142
+ * Fetch and merge context across every scope with an identifier, in one call.
1143
+ *
1144
+ * Mirrors Python's `sdk.fetch()`. The three behaviours worth knowing:
1145
+ *
1146
+ * - Scope fetches run in parallel and a FAILED scope is dropped with a
1147
+ * warning rather than failing the call, so partial context still gets
1148
+ * returned. The one exception is `InvalidInputError`: a bad request must
1149
+ * surface instead of degrading into a silently-empty context.
1150
+ * - `user_id` is threaded into the conversation-scope sub-fetch so the
1151
+ * anticipation cache can apply its per-user filter.
1152
+ * - The summary-mode parameters go ONLY to the user-scope sub-fetch. The
1153
+ * other three routes return 422 for `context_mode="conversation-summary"`,
1154
+ * and that rejection would be swallowed by the drop-failed-scope rule.
1155
+ */
1156
+ fetch(options?: UnifiedFetchOptions): Promise<UnifiedContext>;
1157
+ /**
1158
+ * An LLM-ready tool definition that fetches Synap context.
1159
+ *
1160
+ * Mirrors Python's `as_tool()`. The scope identifiers are closed over, so
1161
+ * the model cannot choose whose memory to read.
1162
+ */
1163
+ as_tool(options?: AsToolOptions): ToolDefinition;
1164
+ /**
1165
+ * A read-only diagnostic view of the in-process anticipation cache.
1166
+ *
1167
+ * Mirrors Python's `anticipation_cache_snapshot()`. Returns the same
1168
+ * snake_case shape, since this output gets logged and compared across SDKs.
1169
+ */
1170
+ anticipation_cache_snapshot(): AnticipationCacheSnapshot;
1171
+ /** Release timers and connections. Safe to call more than once. */
1172
+ shutdown(): Promise<void>;
1173
+ }
1174
+
1175
+ /** Kept in sync with package.json by the release workflow. */
1176
+ declare const SDK_VERSION = "1.0.0-beta.0";
1177
+
1178
+ /**
1179
+ * Synap SDK error taxonomy.
1180
+ *
1181
+ * Mirrors `maximem_synap/models/errors.py` 1:1, including the four
1182
+ * backward-compatibility aliases at the bottom. If you add an error to one
1183
+ * SDK, add it to the other in the same change.
1184
+ *
1185
+ * ## Why every error carries a `.code`
1186
+ *
1187
+ * This package ships dual ESM + CJS. A consumer whose dependency graph pulls
1188
+ * in both formats gets *two* copies of every class in this file, and then:
1189
+ *
1190
+ * try { ... } catch (e) { if (e instanceof SynapError) { ... } }
1191
+ *
1192
+ * silently evaluates to `false` against the copy from the other format. Error
1193
+ * handling stops working and nothing throws to tell you. `.code` is a plain
1194
+ * string, so it is immune. Prefer it at package boundaries:
1195
+ *
1196
+ * if (isSynapError(e) && e.code === 'rate_limit') { ... }
1197
+ *
1198
+ * `Symbol.hasInstance` is also wired up on the base class, so `instanceof`
1199
+ * works across realms too -- but `.code` is the documented contract.
1200
+ */
1201
+ /** Stable, format-independent discriminators. Never renumber or reuse. */
1202
+ type SynapErrorCode = 'synap_error' | 'transient' | 'permanent' | 'network_timeout' | 'rate_limit' | 'insufficient_credits' | 'service_unavailable' | 'invalid_input' | 'invalid_instance_id' | 'invalid_conversation_id' | 'authentication' | 'context_not_found' | 'conflict' | 'transcript_conflict' | 'session_expired' | 'listening_already_active' | 'listening_not_active' | 'agent_unavailable';
1203
+ interface SynapErrorOptions {
1204
+ correlationId?: string | null;
1205
+ cause?: unknown;
1206
+ }
1207
+ declare class SynapError extends Error {
1208
+ /**
1209
+ * The single declaration site for each subclass's code.
1210
+ *
1211
+ * It is `static` rather than a class field on purpose. Under
1212
+ * `useDefineForClassFields` (the default at our ES2022 target) a class field
1213
+ * is an *instance* property, so it is invisible from the constructor object.
1214
+ * `Symbol.hasInstance` below only has the constructor to work with, so a
1215
+ * field would leave it unable to tell `AuthenticationError` from
1216
+ * `RateLimitError` -- it would report every Synap error as an instance of
1217
+ * every other one.
1218
+ */
1219
+ static readonly errorCode: SynapErrorCode;
1220
+ /** True for errors where a retry may succeed. Static for the same reason. */
1221
+ static readonly isTransient: boolean;
1222
+ /** Own, enumerable, so logs and JSON.stringify include it. */
1223
+ readonly code: SynapErrorCode;
1224
+ readonly correlationId: string | null;
1225
+ readonly transient: boolean;
1226
+ constructor(message: string, options?: SynapErrorOptions);
1227
+ static [Symbol.hasInstance](instance: unknown): boolean;
1228
+ }
1229
+ /** Type guard that works across ESM/CJS copies and realms. */
1230
+ declare function isSynapError(e: unknown): e is SynapError;
1231
+ declare class SynapTransientError extends SynapError {
1232
+ static readonly errorCode: SynapErrorCode;
1233
+ static readonly isTransient: boolean;
1234
+ }
1235
+ declare class SynapPermanentError extends SynapError {
1236
+ static readonly errorCode: SynapErrorCode;
1237
+ static readonly isTransient: boolean;
1238
+ }
1239
+ declare class NetworkTimeoutError extends SynapTransientError {
1240
+ static readonly errorCode: SynapErrorCode;
1241
+ }
1242
+ declare class RateLimitError extends SynapTransientError {
1243
+ static readonly errorCode: SynapErrorCode;
1244
+ readonly retryAfterSeconds: number | null;
1245
+ constructor(message: string, options?: SynapErrorOptions & {
1246
+ retryAfterSeconds?: number | null;
1247
+ });
1248
+ }
1249
+ declare class ServiceUnavailableError extends SynapTransientError {
1250
+ static readonly errorCode: SynapErrorCode;
1251
+ }
1252
+ declare class AgentUnavailableError extends SynapTransientError {
1253
+ static readonly errorCode: SynapErrorCode;
1254
+ constructor(message?: string, options?: SynapErrorOptions);
1255
+ }
1256
+ /**
1257
+ * The caller's credit wallet cannot cover this request.
1258
+ *
1259
+ * HTTP 402, and gRPC RESOURCE_EXHAUSTED with credit-related trailing metadata.
1260
+ * Permanent by design: retrying without topping up burns quota and never
1261
+ * succeeds. This is the error behind "the agent forgot" reports where retrieval
1262
+ * silently returns nothing.
1263
+ */
1264
+ declare class InsufficientCreditsError extends SynapPermanentError {
1265
+ static readonly errorCode: SynapErrorCode;
1266
+ readonly balanceCredits: number | null;
1267
+ readonly requiredCredits: number | null;
1268
+ constructor(message: string, options?: SynapErrorOptions & {
1269
+ balanceCredits?: number | null;
1270
+ requiredCredits?: number | null;
1271
+ });
1272
+ }
1273
+ declare class InvalidInputError extends SynapPermanentError {
1274
+ static readonly errorCode: SynapErrorCode;
1275
+ }
1276
+ /**
1277
+ * Raised by `validateInstanceId` before any network call, matching Python's
1278
+ * `utils/validators.py`. The message text is identical in both SDKs.
1279
+ */
1280
+ declare class InvalidInstanceIdError extends InvalidInputError {
1281
+ static readonly errorCode: SynapErrorCode;
1282
+ }
1283
+ /** See the note on InvalidInstanceIdError. Raised by `validateConversationId`. */
1284
+ declare class InvalidConversationIdError extends InvalidInputError {
1285
+ static readonly errorCode: SynapErrorCode;
1286
+ }
1287
+ declare class AuthenticationError extends SynapPermanentError {
1288
+ static readonly errorCode: SynapErrorCode;
1289
+ }
1290
+ declare class ContextNotFoundError extends SynapPermanentError {
1291
+ static readonly errorCode: SynapErrorCode;
1292
+ }
1293
+ declare class ConflictError extends SynapPermanentError {
1294
+ static readonly errorCode: SynapErrorCode;
1295
+ }
1296
+ declare class TranscriptConflictError extends ConflictError {
1297
+ static readonly errorCode: SynapErrorCode;
1298
+ }
1299
+ declare class SessionExpiredError extends SynapPermanentError {
1300
+ static readonly errorCode: SynapErrorCode;
1301
+ }
1302
+ declare class ListeningAlreadyActiveError extends SynapPermanentError {
1303
+ static readonly errorCode: SynapErrorCode;
1304
+ }
1305
+ declare class ListeningNotActiveError extends SynapPermanentError {
1306
+ static readonly errorCode: SynapErrorCode;
1307
+ }
1308
+ declare const SDKError: typeof SynapError;
1309
+ declare const TransientError: typeof SynapTransientError;
1310
+ declare const PermanentError: typeof SynapPermanentError;
1311
+ declare const ConnectionError: typeof NetworkTimeoutError;
1312
+
1313
+ /**
1314
+ * Scope-aware context fetching.
1315
+ *
1316
+ * Scopes are client > customer > user. `conversation_id` is NOT a tier: it
1317
+ * groups turns within a scope. Passing it narrows an existing scope rather
1318
+ * than selecting a different one.
1319
+ */
1320
+
1321
+ type Scope = 'user' | 'customer' | 'client' | 'conversation';
1322
+
1323
+ /**
1324
+ * BM25 scorer for the anticipation cache.
1325
+ *
1326
+ * A faithful port of `maximem_synap/cache/bm25.py`. Every tuned value comes
1327
+ * from the shared behavior contract, so the two implementations cannot drift
1328
+ * on parameters. What they *can* still drift on is arithmetic, which is what
1329
+ * the shared conformance corpus is for.
1330
+ */
1331
+ declare function stem(word: string): string;
1332
+ /** Tokenize, lowercase, remove stop words, and stem. Alias labels survive whole. */
1333
+ declare function tokenize(text: string | null | undefined): string[];
1334
+ /** Okapi BM25 scorer over a small in-memory corpus. */
1335
+ declare class BM25 {
1336
+ static readonly IDF_FLOOR: number;
1337
+ readonly k1: number;
1338
+ readonly b: number;
1339
+ readonly corpus: readonly (readonly string[])[];
1340
+ readonly docCount: number;
1341
+ readonly avgdl: number;
1342
+ readonly docLens: readonly number[];
1343
+ readonly idf: ReadonlyMap<string, number>;
1344
+ constructor(corpus: readonly (readonly string[])[], k1?: number, b?: number);
1345
+ score(queryTokens: readonly string[], docIdx: number): number;
1346
+ scores(queryTokens: readonly string[]): number[];
1347
+ }
1348
+
1349
+ /**
1350
+ * Loader for the shared SDK behavior contract.
1351
+ *
1352
+ * The values here are tuned against measured eval failures and are shared,
1353
+ * byte-identical, with the Python SDK. They live in data rather than code
1354
+ * specifically so the two SDKs cannot drift. See
1355
+ * `synap/sdk/CONTRACT/README.md`.
1356
+ *
1357
+ * `anticipation.json` is vendored into `src/` by
1358
+ * `synap/sdk/scripts/sync-behavior.sh` and imported (not read from disk) so
1359
+ * that the bundler inlines it. Reading it with `node:fs` at runtime would work
1360
+ * in Node and break in every bundled target: Edge, Workers, and anything under
1361
+ * webpack/turbopack, where `dist/` is rewritten and relative paths do not
1362
+ * survive. It is the same failure mode as `@grpc/proto-loader` reading a
1363
+ * `.proto` off disk (gotcha G-P).
1364
+ */
1365
+ interface RecallPattern {
1366
+ pattern: string;
1367
+ flags: string;
1368
+ note: string;
1369
+ }
1370
+ interface BehaviorContract {
1371
+ version: number;
1372
+ recall_bypass: {
1373
+ note: string;
1374
+ env_flag: string;
1375
+ default: boolean;
1376
+ patterns: RecallPattern[];
1377
+ };
1378
+ thresholds: {
1379
+ bm25: {
1380
+ value: number;
1381
+ note: string;
1382
+ };
1383
+ novel_term: {
1384
+ value: number;
1385
+ note: string;
1386
+ };
1387
+ min_corpus_for_novel_gate: {
1388
+ value: number;
1389
+ note: string;
1390
+ };
1391
+ effective_floor: {
1392
+ value: number;
1393
+ note: string;
1394
+ };
1395
+ query_token_scale: {
1396
+ value: number;
1397
+ note: string;
1398
+ };
1399
+ };
1400
+ coverage_gate: {
1401
+ note: string;
1402
+ env_flag: string;
1403
+ default: number | null;
1404
+ empty_query_coverage: number;
1405
+ };
1406
+ telemetry: {
1407
+ rejected_items_cap: {
1408
+ value: number;
1409
+ note: string;
1410
+ };
1411
+ content_preview_chars: number;
1412
+ };
1413
+ bm25: {
1414
+ k1: number;
1415
+ b: number;
1416
+ idf_floor: number;
1417
+ min_token_len: number;
1418
+ min_stem_len: number;
1419
+ suffixes: string[];
1420
+ stop_words: string[];
1421
+ alias_token: {
1422
+ pattern: string;
1423
+ note: string;
1424
+ };
1425
+ };
1426
+ }
1427
+ declare const CONTRACT: BehaviorContract;
1428
+ declare const THRESHOLDS: {
1429
+ readonly bm25: number;
1430
+ readonly novelTerm: number;
1431
+ readonly minCorpusForNovelGate: number;
1432
+ /** Lower bound on the derived per-query threshold. */
1433
+ readonly effectiveFloor: number;
1434
+ /** Points of score allowed per query token when deriving the threshold. */
1435
+ readonly queryTokenScale: number;
1436
+ };
1437
+ /** True when any query string looks like an explicit memory-recall ask. */
1438
+ declare function isRecallQuery(searchQuery: readonly (string | null | undefined)[] | null | undefined): boolean;
1439
+
1440
+ /**
1441
+ * @maximem/synap-js-sdk
1442
+ *
1443
+ * Native TypeScript SDK for Synap. No Python runtime required.
1444
+ */
1445
+
1446
+ /**
1447
+ * The duck-typed contract the TS integrations rely on.
1448
+ *
1449
+ * `synap-mastra`, `synap-eve` and `synap-claude-agent-ts` each redeclare this
1450
+ * shape locally today. Exporting it here means they can `import type` it
1451
+ * instead, so a change to the client's surface breaks their build rather than
1452
+ * their runtime.
1453
+ */
1454
+ interface SynapSdkLike {
1455
+ user: {
1456
+ context: {
1457
+ fetch: (options?: Record<string, unknown>) => Promise<unknown>;
1458
+ };
1459
+ };
1460
+ customer: {
1461
+ context: {
1462
+ fetch: (options?: Record<string, unknown>) => Promise<unknown>;
1463
+ };
1464
+ };
1465
+ client: {
1466
+ context: {
1467
+ fetch: (options?: Record<string, unknown>) => Promise<unknown>;
1468
+ };
1469
+ };
1470
+ memories: {
1471
+ create: (options: {
1472
+ document: string;
1473
+ } & Record<string, unknown>) => Promise<unknown>;
1474
+ };
1475
+ }
1476
+
1477
+ export { AgentUnavailableError, type AnthropicToolDefinition, AnticipationCacheSnapshot, type AsToolOptions, AuthenticationError, BM25, CONTRACT, type CacheNamespace, type CacheStats, type CompactOptions, type CompactionCallback, type ConfigureOptions, ConflictError, ConnectionError, type ContextMetadata, ContextNotFoundError, type ConversationContext, type ConversationNamespace, type CreateFromFileOptions, type CreateMemoryOptions, type CreateMemoryResult, type Credentials, type CreditsNamespace, DEFAULT_BASE_URL, DEFAULT_DOCUMENT_TYPE, DEFAULT_INGEST_MODE, DEFAULT_RETRY_POLICY, DEFAULT_TIMEOUTS, DOCUMENT_TYPES, type DocumentType, ENDPOINTS, type Emotion, type EndpointName, type EndpointSpec, type Episode, type EstimateOptions, type Fact, type FetchOptions, type FlatMemory, type FormatOptions, HttpTransport, type HttpTransportOptions, INGEST_MODES, ITEM_COLLECTIONS, type IngestMode, type IngestTranscriptOptions, type InstanceNamespace, InsufficientCreditsError, InvalidConversationIdError, InvalidInputError, InvalidInstanceIdError, type ItemCollection, type Json, type ListenOptions, ListeningAlreadyActiveError, ListeningNotActiveError, type MemoriesInterface, NetworkTimeoutError, type NormalisedContext, type OpenAiToolDefinition, PermanentError, type Preference, RateLimitError, type RawContext, type RawContextItem, type RawConversationContext, type RecordMessageOptions, type RecordThinkingOptions, type RequestOptions, type RetryPolicy, SDKError, SDK_VERSION, type Scope, type SendMessageOptions, ServiceUnavailableError, SessionExpiredError, SynapClient, type SynapClientOptions, SynapError, type SynapErrorCode, type SynapErrorOptions, SynapPermanentError, type SynapSdkLike, SynapTransientError, THRESHOLDS, type TemporalEvent, type TemporalFields, type TimeoutConfig, type ToolDefinition, type ToolHandler, type ToolScope, type ToolStyle, TranscriptConflictError, type TranscriptTurn, TransientError, type UnifiedContext, type UnifiedFetchOptions, type UpdateMemoryOptions, type UserNamespace, type WaitOptions, type WriteInvalidator, flattenContextItems, formatForPrompt, isOutcomeUnknown, isRecallQuery, isSynapError, mergeScopeResults, resolvePath, shouldRetry, stem, tokenize };