@maximem/synap-js-sdk 0.2.3 → 0.3.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.
package/README.md CHANGED
@@ -37,14 +37,13 @@ Windows:
37
37
  $env:USERPROFILE\.synap-js-sdk\.venv\Scripts\python.exe -c "import maximem_synap; print(maximem_synap.__version__)"
38
38
  ```
39
39
 
40
- ## Required Environment Variables
40
+ ## Required Environment Variable
41
41
 
42
42
  - `SYNAP_API_KEY` (from the Synap dashboard — Instances → Generate API Key)
43
- - `SYNAP_INSTANCE_ID`
44
- - `SYNAP_BASE_URL`
45
- - `SYNAP_GRPC_HOST`
46
- - `SYNAP_GRPC_PORT`
47
- - `SYNAP_GRPC_TLS`
43
+
44
+ The SDK uses the default Synap cloud endpoints automatically. `SYNAP_BASE_URL`,
45
+ `SYNAP_GRPC_HOST`, `SYNAP_GRPC_PORT`, and `SYNAP_GRPC_TLS` are only needed for
46
+ advanced overrides such as local development or custom environments.
48
47
 
49
48
  ## Quick Start (JavaScript)
50
49
 
@@ -52,12 +51,7 @@ $env:USERPROFILE\.synap-js-sdk\.venv\Scripts\python.exe -c "import maximem_synap
52
51
  const { createClient } = require('@maximem/synap-js-sdk');
53
52
 
54
53
  const synap = createClient({
55
- instanceId: process.env.SYNAP_INSTANCE_ID,
56
54
  apiKey: process.env.SYNAP_API_KEY,
57
- baseUrl: process.env.SYNAP_BASE_URL,
58
- grpcHost: process.env.SYNAP_GRPC_HOST,
59
- grpcPort: Number(process.env.SYNAP_GRPC_PORT || 50051),
60
- grpcUseTls: process.env.SYNAP_GRPC_TLS === 'true',
61
55
  });
62
56
 
63
57
  async function run() {
@@ -5,9 +5,9 @@ Protocol:
5
5
  stdin -> {"id": 1, "method": "init", "params": {...}}\n
6
6
  stdout <- {"id": 1, "result": {...}, "error": null}\n
7
7
  Methods:
8
- init, add_memory, search_memory, get_memories, fetch_user_context,
9
- fetch_customer_context, fetch_client_context, get_context_for_prompt,
10
- delete_memory, shutdown
8
+ init, add_memory, create_memory, record_message, search_memory, get_memories,
9
+ fetch_user_context, fetch_customer_context, fetch_client_context,
10
+ get_context_for_prompt, delete_memory, shutdown
11
11
  """
12
12
 
13
13
  import asyncio
@@ -637,9 +637,69 @@ async def handle_shutdown(_params: dict) -> dict:
637
637
  }
638
638
 
639
639
 
640
+ async def handle_record_message(params: dict) -> dict:
641
+ """Record a single conversation turn (registers the conversation_id).
642
+
643
+ Mirrors sdk.conversation.record_message(...) so the JS client can expose
644
+ the same namespaced surface the integration packages duck-type against.
645
+ """
646
+ handler_start = time.perf_counter()
647
+ timings: List[dict] = []
648
+ start = time.perf_counter()
649
+ step = time.perf_counter()
650
+ result = await sdk.conversation.record_message(
651
+ conversation_id=params["conversation_id"],
652
+ role=params["role"],
653
+ content=params["content"],
654
+ user_id=params["user_id"],
655
+ customer_id=params["customer_id"],
656
+ session_id=params.get("session_id"),
657
+ metadata=params.get("metadata"),
658
+ )
659
+ append_step(timings, "record_message", step)
660
+ return {
661
+ "success": True,
662
+ "latencyMs": ms_since(start),
663
+ "result": result,
664
+ "bridgeTiming": {"python_total_ms": ms_since(handler_start), "steps": timings},
665
+ }
666
+
667
+
668
+ async def handle_create_memory(params: dict) -> dict:
669
+ """Create a durable memory from a single document string.
670
+
671
+ Mirrors sdk.memories.create(document=...). Distinct from add_memory,
672
+ which builds a transcript from a messages array.
673
+ """
674
+ handler_start = time.perf_counter()
675
+ timings: List[dict] = []
676
+ start = time.perf_counter()
677
+ create_kwargs: dict = {"document": params["document"]}
678
+ for key in ("user_id", "customer_id", "document_type", "document_id",
679
+ "document_created_at", "mode", "metadata"):
680
+ if params.get(key) is not None:
681
+ create_kwargs[key] = params[key]
682
+ step = time.perf_counter()
683
+ result = await sdk.memories.create(**create_kwargs)
684
+ append_step(timings, "memories_create", step)
685
+ status = result.status.value if hasattr(result.status, "value") else result.status
686
+ return {
687
+ "success": True,
688
+ "latencyMs": ms_since(start),
689
+ "result": {
690
+ "ingestion_id": str(result.ingestion_id),
691
+ "document_id": result.document_id,
692
+ "status": status,
693
+ },
694
+ "bridgeTiming": {"python_total_ms": ms_since(handler_start), "steps": timings},
695
+ }
696
+
697
+
640
698
  HANDLERS = {
641
699
  "init": handle_init,
642
700
  "add_memory": handle_add_memory,
701
+ "create_memory": handle_create_memory,
702
+ "record_message": handle_record_message,
643
703
  "search_memory": handle_search_memory,
644
704
  "get_memories": handle_get_memories,
645
705
  "fetch_user_context": handle_fetch_user_context,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maximem/synap-js-sdk",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "JavaScript wrapper around the Synap Python SDK",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -19,7 +19,11 @@
19
19
  "scripts": {
20
20
  "setup": "node bin/synap-js-sdk.js setup",
21
21
  "setup:ts": "node bin/synap-js-sdk.js setup-ts",
22
- "check": "node -e \"const sdk=require('./src'); console.log(Object.keys(sdk));\""
22
+ "check": "node -e \"const sdk=require('./src'); console.log(Object.keys(sdk));\"",
23
+ "test": "vitest run"
24
+ },
25
+ "devDependencies": {
26
+ "vitest": "^2.1.9"
23
27
  },
24
28
  "engines": {
25
29
  "node": ">=18"
package/src/errors.js CHANGED
@@ -79,6 +79,21 @@ class AuthenticationError extends SynapPermanentError {
79
79
  }
80
80
  }
81
81
 
82
+ class InsufficientCreditsError extends SynapPermanentError {
83
+ constructor(message, details, correlationId) {
84
+ super(message, correlationId);
85
+ this.name = 'InsufficientCreditsError';
86
+ // Structured details from the server's 402 response body:
87
+ // { balance_credits, minimum_required_credits, recovery_url, redeem_url }
88
+ this.balanceCredits = details && details.balance_credits != null
89
+ ? Number(details.balance_credits) : null;
90
+ this.minimumRequiredCredits = details && details.minimum_required_credits != null
91
+ ? Number(details.minimum_required_credits) : null;
92
+ this.recoveryUrl = details && details.recovery_url ? details.recovery_url : null;
93
+ this.redeemUrl = details && details.redeem_url ? details.redeem_url : null;
94
+ }
95
+ }
96
+
82
97
  class ContextNotFoundError extends SynapPermanentError {
83
98
  constructor(message, correlationId) {
84
99
  super(message, correlationId);
@@ -119,6 +134,7 @@ const ERROR_MAP = {
119
134
  InvalidInstanceIdError,
120
135
  InvalidConversationIdError,
121
136
  AuthenticationError,
137
+ InsufficientCreditsError,
122
138
  ContextNotFoundError,
123
139
  SessionExpiredError,
124
140
  ListeningAlreadyActiveError,
@@ -143,6 +159,7 @@ module.exports = {
143
159
  InvalidInstanceIdError,
144
160
  InvalidConversationIdError,
145
161
  AuthenticationError,
162
+ InsufficientCreditsError,
146
163
  ContextNotFoundError,
147
164
  SessionExpiredError,
148
165
  ListeningAlreadyActiveError,
@@ -203,6 +203,7 @@ class SynapClient {
203
203
  };
204
204
 
205
205
  this.#registerShutdownHooks();
206
+ this.#installNamespacedApi();
206
207
  }
207
208
 
208
209
  async init() {
@@ -358,6 +359,106 @@ class SynapClient {
358
359
  process.exit(0);
359
360
  });
360
361
  }
362
+
363
+ #installNamespacedApi() {
364
+ const buildParams = (args = {}) => {
365
+ const params = {};
366
+ const conv = pickDefined(args.conversation_id, args.conversationId);
367
+ const sq = pickDefined(args.search_query, args.searchQuery);
368
+ const mr = pickDefined(args.max_results, args.maxResults);
369
+ if (conv !== undefined && conv !== null) params.conversation_id = conv;
370
+ if (sq !== undefined && sq !== null) params.search_query = sq;
371
+ if (mr !== undefined) params.max_results = mr;
372
+ if (args.types !== undefined) params.types = args.types;
373
+ if (args.mode !== undefined) params.mode = args.mode;
374
+ return params;
375
+ };
376
+ const fetch = async (args = {}) => {
377
+ const userId = pickDefined(args.user_id, args.userId);
378
+ const customerId = pickDefined(args.customer_id, args.customerId);
379
+ const params = buildParams(args);
380
+ let command;
381
+ if (userId !== undefined && userId !== null) {
382
+ command = 'fetch_user_context';
383
+ params.user_id = userId;
384
+ if (customerId !== undefined && customerId !== null) params.customer_id = customerId;
385
+ } else if (customerId !== undefined && customerId !== null) {
386
+ command = 'fetch_customer_context';
387
+ params.customer_id = customerId;
388
+ } else {
389
+ command = 'fetch_client_context';
390
+ }
391
+ const result = await this.bridge.call(command, params);
392
+ return result.context || {};
393
+ };
394
+ const fetchScope = (command, idKeys) => async (args = {}) => {
395
+ const params = buildParams(args);
396
+ if (idKeys.includes('user_id')) {
397
+ const userId = pickDefined(args.user_id, args.userId);
398
+ if (userId !== undefined && userId !== null) params.user_id = userId;
399
+ }
400
+ if (idKeys.includes('customer_id')) {
401
+ const customerId = pickDefined(args.customer_id, args.customerId);
402
+ if (customerId !== undefined && customerId !== null) params.customer_id = customerId;
403
+ }
404
+ const result = await this.bridge.call(command, params);
405
+ return result.context || {};
406
+ };
407
+ this.fetch = fetch;
408
+ this.conversation = {
409
+ record_message: async (args = {}) => {
410
+ const conversationId = pickDefined(args.conversation_id, args.conversationId);
411
+ const userId = pickDefined(args.user_id, args.userId);
412
+ const customerId = pickDefined(args.customer_id, args.customerId);
413
+ this.#assert(conversationId, 'conversation_id is required');
414
+ this.#assert(userId, 'user_id is required');
415
+ this.#assert(customerId, 'customer_id is required');
416
+ const params = {
417
+ conversation_id: conversationId, role: args.role, content: args.content,
418
+ user_id: userId, customer_id: customerId,
419
+ };
420
+ const sessionId = pickDefined(args.session_id, args.sessionId);
421
+ if (sessionId !== undefined) params.session_id = sessionId;
422
+ if (args.metadata !== undefined) params.metadata = args.metadata;
423
+ const result = await this.bridge.call('record_message', params);
424
+ return pickDefined(result.result, result);
425
+ },
426
+ context: {
427
+ get_context_for_prompt: async (args = {}) => {
428
+ const conversationId = pickDefined(args.conversation_id, args.conversationId);
429
+ this.#assert(conversationId, 'conversation_id is required');
430
+ const params = { conversation_id: conversationId };
431
+ if (args.style !== undefined) params.style = args.style;
432
+ const result = await this.bridge.call('get_context_for_prompt', params);
433
+ return pickDefined(result.context_for_prompt, result.contextForPrompt, {});
434
+ },
435
+ fetch,
436
+ },
437
+ };
438
+ this.memories = {
439
+ create: async (args = {}) => {
440
+ this.#assert(args.document, 'document is required');
441
+ const params = { document: args.document };
442
+ const userId = pickDefined(args.user_id, args.userId);
443
+ const customerId = pickDefined(args.customer_id, args.customerId);
444
+ const documentType = pickDefined(args.document_type, args.documentType);
445
+ const documentId = pickDefined(args.document_id, args.documentId);
446
+ const documentCreatedAt = pickDefined(args.document_created_at, args.documentCreatedAt);
447
+ if (userId !== undefined) params.user_id = userId;
448
+ if (customerId !== undefined) params.customer_id = customerId;
449
+ if (documentType !== undefined) params.document_type = documentType;
450
+ if (documentId !== undefined) params.document_id = documentId;
451
+ if (documentCreatedAt !== undefined) params.document_created_at = documentCreatedAt;
452
+ if (args.mode !== undefined) params.mode = args.mode;
453
+ if (args.metadata !== undefined) params.metadata = args.metadata;
454
+ const result = await this.bridge.call('create_memory', params, this.options.ingestTimeoutMs);
455
+ return pickDefined(result.result, result);
456
+ },
457
+ };
458
+ this.user = { context: { fetch: fetchScope('fetch_user_context', ['user_id', 'customer_id']) } };
459
+ this.customer = { context: { fetch: fetchScope('fetch_customer_context', ['customer_id']) } };
460
+ this.client = { context: { fetch: fetchScope('fetch_client_context', []) } };
461
+ }
361
462
  }
362
463
 
363
464
  module.exports = { SynapClient };
package/types/index.d.ts CHANGED
@@ -29,7 +29,6 @@ export interface BridgeLogHandler {
29
29
  }
30
30
 
31
31
  export interface SynapClientOptions {
32
- instanceId?: string;
33
32
  apiKey?: string;
34
33
  baseUrl?: string;
35
34
  grpcHost?: string;
@@ -297,6 +296,75 @@ export interface DeleteMemoryResult {
297
296
  bridgeTiming?: BridgeTiming;
298
297
  }
299
298
 
299
+ export interface SynapNamespacedFetchInput {
300
+ userId?: string | null;
301
+ user_id?: string | null;
302
+ customerId?: string | null;
303
+ customer_id?: string | null;
304
+ conversationId?: string | null;
305
+ conversation_id?: string | null;
306
+ searchQuery?: string[] | null;
307
+ search_query?: string[] | null;
308
+ maxResults?: number;
309
+ max_results?: number;
310
+ types?: string[];
311
+ mode?: string;
312
+ }
313
+
314
+ export interface SynapRecordMessageInput {
315
+ conversationId?: string | null;
316
+ conversation_id?: string | null;
317
+ role: string;
318
+ content: string;
319
+ userId?: string | null;
320
+ user_id?: string | null;
321
+ customerId?: string | null;
322
+ customer_id?: string | null;
323
+ sessionId?: string | null;
324
+ session_id?: string | null;
325
+ metadata?: Record<string, unknown>;
326
+ }
327
+
328
+ export interface SynapCreateMemoryInput {
329
+ document: string;
330
+ userId?: string | null;
331
+ user_id?: string | null;
332
+ customerId?: string | null;
333
+ customer_id?: string | null;
334
+ documentType?: string | null;
335
+ document_type?: string | null;
336
+ documentId?: string | null;
337
+ document_id?: string | null;
338
+ documentCreatedAt?: string | null;
339
+ document_created_at?: string | null;
340
+ mode?: string | null;
341
+ metadata?: Record<string, unknown>;
342
+ }
343
+
344
+ export interface SynapGetContextForPromptInput {
345
+ conversationId?: string;
346
+ conversation_id?: string;
347
+ style?: string;
348
+ }
349
+
350
+ export interface SynapScopeContextApi {
351
+ context: {
352
+ fetch(input?: SynapNamespacedFetchInput): Promise<any>;
353
+ };
354
+ }
355
+
356
+ export interface SynapConversationApi {
357
+ record_message(input: SynapRecordMessageInput): Promise<any>;
358
+ context: {
359
+ get_context_for_prompt(input: SynapGetContextForPromptInput): Promise<any>;
360
+ fetch(input?: SynapNamespacedFetchInput): Promise<any>;
361
+ };
362
+ }
363
+
364
+ export interface SynapMemoriesApi {
365
+ create(input: SynapCreateMemoryInput): Promise<any>;
366
+ }
367
+
300
368
  export class SynapClient {
301
369
  constructor(options?: SynapClientOptions);
302
370
  init(): Promise<void>;
@@ -309,6 +377,12 @@ export class SynapClient {
309
377
  getContextForPrompt(input: GetContextForPromptInput): Promise<ContextForPromptResult>;
310
378
  deleteMemory(input: DeleteMemoryInput): Promise<DeleteMemoryResult>;
311
379
  shutdown(): Promise<void>;
380
+ fetch(input?: SynapNamespacedFetchInput): Promise<any>;
381
+ readonly conversation: SynapConversationApi;
382
+ readonly memories: SynapMemoriesApi;
383
+ readonly user: SynapScopeContextApi;
384
+ readonly customer: SynapScopeContextApi;
385
+ readonly client: SynapScopeContextApi;
312
386
  }
313
387
 
314
388
  export interface SetupPythonRuntimeOptions {
@@ -370,6 +444,22 @@ export class InvalidInputError extends SynapPermanentError {}
370
444
  export class InvalidInstanceIdError extends InvalidInputError {}
371
445
  export class InvalidConversationIdError extends InvalidInputError {}
372
446
  export class AuthenticationError extends SynapPermanentError {}
447
+ export class InsufficientCreditsError extends SynapPermanentError {
448
+ balanceCredits: number | null;
449
+ minimumRequiredCredits: number | null;
450
+ recoveryUrl: string | null;
451
+ redeemUrl: string | null;
452
+ constructor(
453
+ message: string,
454
+ details?: {
455
+ balance_credits?: number | null;
456
+ minimum_required_credits?: number | null;
457
+ recovery_url?: string | null;
458
+ redeem_url?: string | null;
459
+ },
460
+ correlationId?: string,
461
+ );
462
+ }
373
463
  export class ContextNotFoundError extends SynapPermanentError {}
374
464
  export class SessionExpiredError extends SynapPermanentError {}
375
465
  export class ListeningAlreadyActiveError extends SynapPermanentError {}