@maximem/synap-js-sdk 0.1.4 → 0.2.1

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/src/errors.js ADDED
@@ -0,0 +1,187 @@
1
+ 'use strict';
2
+
3
+ class SynapError extends Error {
4
+ constructor(message, correlationId) {
5
+ super(message);
6
+ this.name = 'SynapError';
7
+ this.correlationId = correlationId || null;
8
+ }
9
+ }
10
+
11
+ class SynapTransientError extends SynapError {
12
+ constructor(message, correlationId) {
13
+ super(message, correlationId);
14
+ this.name = 'SynapTransientError';
15
+ }
16
+ }
17
+
18
+ class SynapPermanentError extends SynapError {
19
+ constructor(message, correlationId) {
20
+ super(message, correlationId);
21
+ this.name = 'SynapPermanentError';
22
+ }
23
+ }
24
+
25
+ class NetworkTimeoutError extends SynapTransientError {
26
+ constructor(message, correlationId) {
27
+ super(message, correlationId);
28
+ this.name = 'NetworkTimeoutError';
29
+ }
30
+ }
31
+
32
+ class RateLimitError extends SynapTransientError {
33
+ constructor(message, retryAfterSeconds, correlationId) {
34
+ super(message, correlationId);
35
+ this.name = 'RateLimitError';
36
+ this.retryAfterSeconds = retryAfterSeconds || null;
37
+ }
38
+ }
39
+
40
+ class ServiceUnavailableError extends SynapTransientError {
41
+ constructor(message, correlationId) {
42
+ super(message, correlationId);
43
+ this.name = 'ServiceUnavailableError';
44
+ }
45
+ }
46
+
47
+ class AgentUnavailableError extends SynapTransientError {
48
+ constructor(message, correlationId) {
49
+ super(message, correlationId);
50
+ this.name = 'AgentUnavailableError';
51
+ }
52
+ }
53
+
54
+ class InvalidInputError extends SynapPermanentError {
55
+ constructor(message, correlationId) {
56
+ super(message, correlationId);
57
+ this.name = 'InvalidInputError';
58
+ }
59
+ }
60
+
61
+ class InvalidInstanceIdError extends InvalidInputError {
62
+ constructor(message, correlationId) {
63
+ super(message, correlationId);
64
+ this.name = 'InvalidInstanceIdError';
65
+ }
66
+ }
67
+
68
+ class InvalidConversationIdError extends InvalidInputError {
69
+ constructor(message, correlationId) {
70
+ super(message, correlationId);
71
+ this.name = 'InvalidConversationIdError';
72
+ }
73
+ }
74
+
75
+ class AuthenticationError extends SynapPermanentError {
76
+ constructor(message, correlationId) {
77
+ super(message, correlationId);
78
+ this.name = 'AuthenticationError';
79
+ }
80
+ }
81
+
82
+ class CertificateExpiredError extends AuthenticationError {
83
+ constructor(message, correlationId) {
84
+ super(message, correlationId);
85
+ this.name = 'CertificateExpiredError';
86
+ }
87
+ }
88
+
89
+ class CertificateRenewalError extends AuthenticationError {
90
+ constructor(message, correlationId) {
91
+ super(message, correlationId);
92
+ this.name = 'CertificateRenewalError';
93
+ }
94
+ }
95
+
96
+ class BootstrapKeyInvalidError extends AuthenticationError {
97
+ constructor(message, correlationId) {
98
+ super(message, correlationId);
99
+ this.name = 'BootstrapKeyInvalidError';
100
+ }
101
+ }
102
+
103
+ class BootstrapError extends SynapPermanentError {
104
+ constructor(message, correlationId) {
105
+ super(message, correlationId);
106
+ this.name = 'BootstrapError';
107
+ }
108
+ }
109
+
110
+ class ContextNotFoundError extends SynapPermanentError {
111
+ constructor(message, correlationId) {
112
+ super(message, correlationId);
113
+ this.name = 'ContextNotFoundError';
114
+ }
115
+ }
116
+
117
+ class SessionExpiredError extends SynapPermanentError {
118
+ constructor(message, correlationId) {
119
+ super(message, correlationId);
120
+ this.name = 'SessionExpiredError';
121
+ }
122
+ }
123
+
124
+ class ListeningAlreadyActiveError extends SynapPermanentError {
125
+ constructor(message, correlationId) {
126
+ super(message, correlationId);
127
+ this.name = 'ListeningAlreadyActiveError';
128
+ }
129
+ }
130
+
131
+ class ListeningNotActiveError extends SynapPermanentError {
132
+ constructor(message, correlationId) {
133
+ super(message, correlationId);
134
+ this.name = 'ListeningNotActiveError';
135
+ }
136
+ }
137
+
138
+ const ERROR_MAP = {
139
+ SynapError,
140
+ SynapTransientError,
141
+ SynapPermanentError,
142
+ NetworkTimeoutError,
143
+ RateLimitError,
144
+ ServiceUnavailableError,
145
+ AgentUnavailableError,
146
+ InvalidInputError,
147
+ InvalidInstanceIdError,
148
+ InvalidConversationIdError,
149
+ AuthenticationError,
150
+ CertificateExpiredError,
151
+ CertificateRenewalError,
152
+ BootstrapKeyInvalidError,
153
+ BootstrapError,
154
+ ContextNotFoundError,
155
+ SessionExpiredError,
156
+ ListeningAlreadyActiveError,
157
+ ListeningNotActiveError,
158
+ };
159
+
160
+ function createSynapError(message, errorType) {
161
+ const ErrorClass = ERROR_MAP[errorType];
162
+ if (ErrorClass) return new ErrorClass(message);
163
+ return new SynapError(message);
164
+ }
165
+
166
+ module.exports = {
167
+ SynapError,
168
+ SynapTransientError,
169
+ SynapPermanentError,
170
+ NetworkTimeoutError,
171
+ RateLimitError,
172
+ ServiceUnavailableError,
173
+ AgentUnavailableError,
174
+ InvalidInputError,
175
+ InvalidInstanceIdError,
176
+ InvalidConversationIdError,
177
+ AuthenticationError,
178
+ CertificateExpiredError,
179
+ CertificateRenewalError,
180
+ BootstrapKeyInvalidError,
181
+ BootstrapError,
182
+ ContextNotFoundError,
183
+ SessionExpiredError,
184
+ ListeningAlreadyActiveError,
185
+ ListeningNotActiveError,
186
+ createSynapError,
187
+ };
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { SynapClient } = require('./synap-client');
2
2
  const { setupPythonRuntime, resolveInstanceId } = require('./runtime');
3
3
  const { setupTypeScriptExtension } = require('./setup-typescript');
4
+ const errors = require('./errors');
4
5
 
5
6
  module.exports = {
6
7
  SynapClient,
@@ -8,4 +9,5 @@ module.exports = {
8
9
  setupPythonRuntime,
9
10
  setupTypeScriptExtension,
10
11
  resolveInstanceId,
12
+ ...errors,
11
13
  };
@@ -64,6 +64,10 @@ function getWrapperTemplate() {
64
64
  type AddMemoryInput,
65
65
  type SearchMemoryInput,
66
66
  type GetMemoriesInput,
67
+ type FetchUserContextInput,
68
+ type FetchCustomerContextInput,
69
+ type FetchClientContextInput,
70
+ type GetContextForPromptInput,
67
71
  type DeleteMemoryInput,
68
72
  } from '@maximem/synap-js-sdk';
69
73
 
@@ -90,6 +94,22 @@ export class SynapTsClient {
90
94
  return this.client.getMemories(input);
91
95
  }
92
96
 
97
+ fetchUserContext(input: FetchUserContextInput) {
98
+ return this.client.fetchUserContext(input);
99
+ }
100
+
101
+ fetchCustomerContext(input: FetchCustomerContextInput) {
102
+ return this.client.fetchCustomerContext(input);
103
+ }
104
+
105
+ fetchClientContext(input?: FetchClientContextInput) {
106
+ return this.client.fetchClientContext(input);
107
+ }
108
+
109
+ getContextForPrompt(input: GetContextForPromptInput) {
110
+ return this.client.getContextForPrompt(input);
111
+ }
112
+
93
113
  deleteMemory(input: DeleteMemoryInput) {
94
114
  return this.client.deleteMemory(input);
95
115
  }
@@ -1,5 +1,200 @@
1
1
  const { BridgeManager } = require('./bridge-manager');
2
2
 
3
+ function pickDefined(...values) {
4
+ for (const value of values) {
5
+ if (value !== undefined) return value;
6
+ }
7
+ return undefined;
8
+ }
9
+
10
+ function normalizeTemporalFields(item = {}) {
11
+ return {
12
+ eventDate: pickDefined(item.eventDate, item.event_date, null),
13
+ validUntil: pickDefined(item.validUntil, item.valid_until, null),
14
+ temporalCategory: pickDefined(item.temporalCategory, item.temporal_category, null),
15
+ temporalConfidence: pickDefined(item.temporalConfidence, item.temporal_confidence, 0),
16
+ };
17
+ }
18
+
19
+ function normalizeMemoryItem(item = {}) {
20
+ return {
21
+ id: item.id || '',
22
+ memory: pickDefined(item.memory, item.content, ''),
23
+ score: item.score,
24
+ source: item.source,
25
+ metadata: item.metadata || {},
26
+ contextType: pickDefined(item.contextType, item.context_type),
27
+ ...normalizeTemporalFields(item),
28
+ };
29
+ }
30
+
31
+ function normalizeContextMetadata(metadata = {}) {
32
+ return {
33
+ correlationId: pickDefined(metadata.correlationId, metadata.correlation_id, ''),
34
+ ttlSeconds: pickDefined(metadata.ttlSeconds, metadata.ttl_seconds, 0),
35
+ source: metadata.source || 'unknown',
36
+ retrievedAt: pickDefined(metadata.retrievedAt, metadata.retrieved_at, null),
37
+ compactionApplied: pickDefined(metadata.compactionApplied, metadata.compaction_applied, null),
38
+ };
39
+ }
40
+
41
+ function normalizeConversationContext(value) {
42
+ if (!value) return null;
43
+ return {
44
+ summary: value.summary || null,
45
+ currentState: pickDefined(value.currentState, value.current_state, {}),
46
+ keyExtractions: pickDefined(value.keyExtractions, value.key_extractions, {}),
47
+ recentTurns: pickDefined(value.recentTurns, value.recent_turns, []),
48
+ compactionId: pickDefined(value.compactionId, value.compaction_id, null),
49
+ compactedAt: pickDefined(value.compactedAt, value.compacted_at, null),
50
+ conversationId: pickDefined(value.conversationId, value.conversation_id, null),
51
+ };
52
+ }
53
+
54
+ function normalizeFact(item = {}) {
55
+ return {
56
+ id: item.id || '',
57
+ content: item.content || '',
58
+ confidence: pickDefined(item.confidence, 0),
59
+ source: item.source || '',
60
+ extractedAt: pickDefined(item.extractedAt, item.extracted_at, null),
61
+ metadata: item.metadata || {},
62
+ ...normalizeTemporalFields(item),
63
+ };
64
+ }
65
+
66
+ function normalizePreference(item = {}) {
67
+ return {
68
+ id: item.id || '',
69
+ category: item.category || '',
70
+ content: item.content || '',
71
+ strength: pickDefined(item.strength, item.confidence, 0),
72
+ source: item.source || '',
73
+ extractedAt: pickDefined(item.extractedAt, item.extracted_at, null),
74
+ metadata: item.metadata || {},
75
+ ...normalizeTemporalFields(item),
76
+ };
77
+ }
78
+
79
+ function normalizeEpisode(item = {}) {
80
+ return {
81
+ id: item.id || '',
82
+ summary: pickDefined(item.summary, item.content, ''),
83
+ occurredAt: pickDefined(item.occurredAt, item.occurred_at, null),
84
+ significance: pickDefined(item.significance, item.confidence, 0),
85
+ participants: item.participants || [],
86
+ metadata: item.metadata || {},
87
+ ...normalizeTemporalFields(item),
88
+ };
89
+ }
90
+
91
+ function normalizeEmotion(item = {}) {
92
+ return {
93
+ id: item.id || '',
94
+ emotionType: pickDefined(item.emotionType, item.emotion_type, ''),
95
+ intensity: pickDefined(item.intensity, item.confidence, 0),
96
+ detectedAt: pickDefined(item.detectedAt, item.detected_at, null),
97
+ context: item.context || '',
98
+ metadata: item.metadata || {},
99
+ ...normalizeTemporalFields(item),
100
+ };
101
+ }
102
+
103
+ function normalizeTemporalEvent(item = {}) {
104
+ return {
105
+ id: item.id || '',
106
+ content: item.content || '',
107
+ eventDate: pickDefined(item.eventDate, item.event_date, null),
108
+ validUntil: pickDefined(item.validUntil, item.valid_until, null),
109
+ temporalCategory: pickDefined(item.temporalCategory, item.temporal_category, ''),
110
+ temporalConfidence: pickDefined(item.temporalConfidence, item.temporal_confidence, 0),
111
+ confidence: pickDefined(item.confidence, 0),
112
+ source: item.source || '',
113
+ extractedAt: pickDefined(item.extractedAt, item.extracted_at, null),
114
+ metadata: item.metadata || {},
115
+ };
116
+ }
117
+
118
+ function normalizeContextResponse(result = {}) {
119
+ const context = result.context || result;
120
+ return {
121
+ facts: (context.facts || []).map(normalizeFact),
122
+ preferences: (context.preferences || []).map(normalizePreference),
123
+ episodes: (context.episodes || []).map(normalizeEpisode),
124
+ emotions: (context.emotions || []).map(normalizeEmotion),
125
+ temporalEvents: (pickDefined(context.temporalEvents, context.temporal_events, []) || []).map(normalizeTemporalEvent),
126
+ conversationContext: normalizeConversationContext(
127
+ pickDefined(context.conversationContext, context.conversation_context, null)
128
+ ),
129
+ metadata: normalizeContextMetadata(context.metadata || {}),
130
+ rawResponse: pickDefined(context.rawResponse, context.raw_response, {}),
131
+ bridgeTiming: result.bridgeTiming,
132
+ };
133
+ }
134
+
135
+ function normalizeRecentMessage(item = {}) {
136
+ return {
137
+ role: item.role || 'user',
138
+ content: item.content || '',
139
+ timestamp: item.timestamp || null,
140
+ messageId: pickDefined(item.messageId, item.message_id, ''),
141
+ };
142
+ }
143
+
144
+ function normalizeContextForPromptResult(result = {}) {
145
+ const payload = pickDefined(result.contextForPrompt, result.context_for_prompt, result);
146
+ return {
147
+ formattedContext: pickDefined(payload.formattedContext, payload.formatted_context, null),
148
+ available: !!payload.available,
149
+ isStale: !!pickDefined(payload.isStale, payload.is_stale, false),
150
+ compressionRatio: pickDefined(payload.compressionRatio, payload.compression_ratio, null),
151
+ validationScore: pickDefined(payload.validationScore, payload.validation_score, null),
152
+ compactionAgeSeconds: pickDefined(payload.compactionAgeSeconds, payload.compaction_age_seconds, null),
153
+ qualityWarning: !!pickDefined(payload.qualityWarning, payload.quality_warning, false),
154
+ recentMessages: (pickDefined(payload.recentMessages, payload.recent_messages, []) || []).map(normalizeRecentMessage),
155
+ recentMessageCount: pickDefined(payload.recentMessageCount, payload.recent_message_count, 0),
156
+ compactedMessageCount: pickDefined(payload.compactedMessageCount, payload.compacted_message_count, 0),
157
+ totalMessageCount: pickDefined(payload.totalMessageCount, payload.total_message_count, 0),
158
+ bridgeTiming: result.bridgeTiming,
159
+ };
160
+ }
161
+
162
+ function normalizeSearchMemoryResult(result = {}) {
163
+ return {
164
+ success: !!result.success,
165
+ latencyMs: result.latencyMs || 0,
166
+ results: (result.results || []).map(normalizeMemoryItem),
167
+ resultsCount: pickDefined(result.resultsCount, result.results?.length, 0),
168
+ rawResponse: result.rawResponse || {},
169
+ source: result.source,
170
+ bridgeTiming: result.bridgeTiming,
171
+ };
172
+ }
173
+
174
+ function normalizeGetMemoriesResult(result = {}) {
175
+ const memories = (result.memories || []).map(normalizeMemoryItem);
176
+ return {
177
+ success: !!result.success,
178
+ latencyMs: result.latencyMs || 0,
179
+ memories,
180
+ totalCount: pickDefined(result.totalCount, result.memoriesCount, memories.length, 0),
181
+ rawResponse: pickDefined(result.rawResponse, null),
182
+ source: result.source,
183
+ bridgeTiming: result.bridgeTiming,
184
+ };
185
+ }
186
+
187
+ function normalizeDeleteMemoryResult(result = {}) {
188
+ return {
189
+ success: !!result.success,
190
+ latencyMs: result.latencyMs || 0,
191
+ deletedCount: pickDefined(result.deletedCount, result.rawResponse?.deleted, 0),
192
+ rawResponse: pickDefined(result.rawResponse, null),
193
+ note: result.note,
194
+ bridgeTiming: result.bridgeTiming,
195
+ };
196
+ }
197
+
3
198
  class SynapClient {
4
199
  constructor(options = {}) {
5
200
  this.bridge = new BridgeManager(options);
@@ -14,42 +209,122 @@ class SynapClient {
14
209
  await this.bridge.ensureStarted();
15
210
  }
16
211
 
17
- async addMemory({ userId, messages, mode }) {
212
+ async addMemory({
213
+ userId,
214
+ customerId,
215
+ conversationId,
216
+ sessionId,
217
+ messages,
218
+ mode,
219
+ documentType,
220
+ documentId,
221
+ documentCreatedAt,
222
+ metadata,
223
+ }) {
18
224
  this.#assert(userId, 'userId is required');
225
+ this.#assert(customerId, 'customerId is required');
19
226
  this.#assert(Array.isArray(messages), 'messages must be an array');
20
227
 
21
228
  const params = { user_id: userId, messages };
229
+ params.customer_id = customerId;
230
+ if (conversationId !== undefined) params.conversation_id = conversationId;
231
+ if (sessionId !== undefined) params.session_id = sessionId;
22
232
  if (mode !== undefined) params.mode = mode;
233
+ if (documentType !== undefined) params.document_type = documentType;
234
+ if (documentId !== undefined) params.document_id = documentId;
235
+ if (documentCreatedAt !== undefined) params.document_created_at = documentCreatedAt;
236
+ if (metadata !== undefined) params.metadata = metadata;
23
237
 
24
238
  return this.bridge.call('add_memory', params, this.options.ingestTimeoutMs);
25
239
  }
26
240
 
27
- async searchMemory({ userId, query, maxResults = 10, mode }) {
241
+ async searchMemory({ userId, customerId, query, maxResults = 10, mode, conversationId, types }) {
28
242
  this.#assert(userId, 'userId is required');
29
243
  this.#assert(query, 'query is required');
244
+ this.#assertArray(types, 'types must be an array when provided');
30
245
 
31
246
  const params = { user_id: userId, query, max_results: maxResults };
247
+ if (customerId !== undefined) params.customer_id = customerId;
32
248
  if (mode !== undefined) params.mode = mode;
249
+ if (conversationId !== undefined) params.conversation_id = conversationId;
250
+ if (types !== undefined) params.types = types;
33
251
 
34
- return this.bridge.call('search_memory', params);
252
+ return normalizeSearchMemoryResult(await this.bridge.call('search_memory', params));
35
253
  }
36
254
 
37
- async getMemories({ userId, mode }) {
255
+ async getMemories({ userId, customerId, mode, conversationId, maxResults, types }) {
38
256
  this.#assert(userId, 'userId is required');
257
+ this.#assertArray(types, 'types must be an array when provided');
39
258
 
40
259
  const params = { user_id: userId };
260
+ if (customerId !== undefined) params.customer_id = customerId;
41
261
  if (mode !== undefined) params.mode = mode;
262
+ if (conversationId !== undefined) params.conversation_id = conversationId;
263
+ if (maxResults !== undefined) params.max_results = maxResults;
264
+ if (types !== undefined) params.types = types;
42
265
 
43
- return this.bridge.call('get_memories', params);
266
+ return normalizeGetMemoriesResult(await this.bridge.call('get_memories', params));
44
267
  }
45
268
 
46
- async deleteMemory({ userId, memoryId = null }) {
269
+ async fetchUserContext({ userId, customerId, conversationId, searchQuery, maxResults = 10, types, mode }) {
47
270
  this.#assert(userId, 'userId is required');
271
+ this.#assertArray(searchQuery, 'searchQuery must be an array when provided');
272
+ this.#assertArray(types, 'types must be an array when provided');
48
273
 
49
- return this.bridge.call('delete_memory', {
50
- user_id: userId,
51
- memory_id: memoryId,
52
- });
274
+ const params = { user_id: userId, max_results: maxResults };
275
+ if (customerId !== undefined) params.customer_id = customerId;
276
+ if (conversationId !== undefined) params.conversation_id = conversationId;
277
+ if (searchQuery !== undefined) params.search_query = searchQuery;
278
+ if (types !== undefined) params.types = types;
279
+ if (mode !== undefined) params.mode = mode;
280
+
281
+ return normalizeContextResponse(await this.bridge.call('fetch_user_context', params));
282
+ }
283
+
284
+ async fetchCustomerContext({ customerId, conversationId, searchQuery, maxResults = 10, types, mode }) {
285
+ this.#assert(customerId, 'customerId is required');
286
+ this.#assertArray(searchQuery, 'searchQuery must be an array when provided');
287
+ this.#assertArray(types, 'types must be an array when provided');
288
+
289
+ const params = { customer_id: customerId, max_results: maxResults };
290
+ if (conversationId !== undefined) params.conversation_id = conversationId;
291
+ if (searchQuery !== undefined) params.search_query = searchQuery;
292
+ if (types !== undefined) params.types = types;
293
+ if (mode !== undefined) params.mode = mode;
294
+
295
+ return normalizeContextResponse(await this.bridge.call('fetch_customer_context', params));
296
+ }
297
+
298
+ async fetchClientContext({ conversationId, searchQuery, maxResults = 10, types, mode } = {}) {
299
+ this.#assertArray(searchQuery, 'searchQuery must be an array when provided');
300
+ this.#assertArray(types, 'types must be an array when provided');
301
+
302
+ const params = { max_results: maxResults };
303
+ if (conversationId !== undefined) params.conversation_id = conversationId;
304
+ if (searchQuery !== undefined) params.search_query = searchQuery;
305
+ if (types !== undefined) params.types = types;
306
+ if (mode !== undefined) params.mode = mode;
307
+
308
+ return normalizeContextResponse(await this.bridge.call('fetch_client_context', params));
309
+ }
310
+
311
+ async getContextForPrompt({ conversationId, style } = {}) {
312
+ this.#assert(conversationId, 'conversationId is required');
313
+
314
+ const params = { conversation_id: conversationId };
315
+ if (style !== undefined) params.style = style;
316
+
317
+ return normalizeContextForPromptResult(await this.bridge.call('get_context_for_prompt', params));
318
+ }
319
+
320
+ async deleteMemory({ userId, customerId, memoryId = null }) {
321
+ this.#assert(userId, 'userId is required');
322
+ if (memoryId == null) this.#assert(customerId, 'customerId is required when memoryId is not provided');
323
+
324
+ const params = { user_id: userId, memory_id: memoryId };
325
+ if (customerId !== undefined) params.customer_id = customerId;
326
+
327
+ return normalizeDeleteMemoryResult(await this.bridge.call('delete_memory', params));
53
328
  }
54
329
 
55
330
  async shutdown() {
@@ -60,6 +335,10 @@ class SynapClient {
60
335
  if (!value) throw new Error(message);
61
336
  }
62
337
 
338
+ #assertArray(value, message) {
339
+ if (value !== undefined && !Array.isArray(value)) throw new Error(message);
340
+ }
341
+
63
342
  #registerShutdownHooks() {
64
343
  const close = async () => {
65
344
  try {