@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 +5 -11
- package/bridge/synap_bridge.py +63 -3
- package/package.json +6 -2
- package/src/errors.js +17 -0
- package/src/synap-client.js +101 -0
- package/types/index.d.ts +91 -1
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
|
|
40
|
+
## Required Environment Variable
|
|
41
41
|
|
|
42
42
|
- `SYNAP_API_KEY` (from the Synap dashboard — Instances → Generate API Key)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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() {
|
package/bridge/synap_bridge.py
CHANGED
|
@@ -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,
|
|
9
|
-
fetch_customer_context, fetch_client_context,
|
|
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.
|
|
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,
|
package/src/synap-client.js
CHANGED
|
@@ -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 {}
|