@maximem/synap-js-sdk 0.4.2 → 0.4.4
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/dist/{chunk-HR5Y3KXS.cjs → chunk-2TVL5IPQ.cjs} +7 -1
- package/dist/chunk-2TVL5IPQ.cjs.map +1 -0
- package/dist/{chunk-VXRCBMCY.cjs → chunk-3JSE64GW.cjs} +1 -1
- package/dist/{chunk-VXRCBMCY.cjs.map → chunk-3JSE64GW.cjs.map} +1 -1
- package/dist/{chunk-T3NZBIV2.js → chunk-OCMM2Z5P.js} +7 -1
- package/dist/chunk-OCMM2Z5P.js.map +1 -0
- package/dist/{chunk-5SC3ELNK.js → chunk-ZU6ZC4SU.js} +1 -1
- package/dist/chunk-ZU6ZC4SU.js.map +1 -0
- package/dist/grpc/index.cjs +7 -7
- package/dist/grpc/index.d.cts +4 -0
- package/dist/grpc/index.d.ts +4 -0
- package/dist/grpc/index.js +3 -3
- package/dist/index.cjs +204 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +186 -20
- package/dist/index.d.ts +186 -20
- package/dist/index.js +124 -14
- package/dist/index.js.map +1 -1
- package/dist/stream-client-6V4FGKZ3.js +7 -0
- package/dist/stream-client-PRTOH62B.cjs +7 -0
- package/dist/stream-client-PRTOH62B.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-5SC3ELNK.js.map +0 -1
- package/dist/chunk-HR5Y3KXS.cjs.map +0 -1
- package/dist/chunk-T3NZBIV2.js.map +0 -1
- package/dist/stream-client-ME5N5NK4.js +0 -7
- package/dist/stream-client-ZN6ADZBR.cjs +0 -7
- package/dist/stream-client-ZN6ADZBR.cjs.map +0 -1
- /package/dist/{stream-client-ME5N5NK4.js.map → stream-client-6V4FGKZ3.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
TranscriptConflictError,
|
|
23
23
|
TransientError,
|
|
24
24
|
isSynapError
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-OCMM2Z5P.js";
|
|
26
26
|
import {
|
|
27
27
|
ANY_SCOPE,
|
|
28
28
|
AnticipationCache,
|
|
@@ -200,6 +200,16 @@ var Heartbeat = class {
|
|
|
200
200
|
}
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
+
// src/scoping.ts
|
|
204
|
+
var B2C_ISOLATION = "equals_customer";
|
|
205
|
+
function checkCustomerId(isolation, customerId, where, instanceId) {
|
|
206
|
+
if (customerId === void 0 || customerId === null || customerId === "") return;
|
|
207
|
+
if (isolation !== B2C_ISOLATION) return;
|
|
208
|
+
throw new InvalidInputError(
|
|
209
|
+
`${where}: customer_id is not accepted on this instance${instanceId ? ` (${instanceId})` : ""}, which is B2C (user_context_isolation='${B2C_ISOLATION}'). Send user_id only; it is the whole identity, and the server files and reads your data under it. Received customer_id=${JSON.stringify(customerId)}.`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
203
213
|
// src/transport/http.ts
|
|
204
214
|
var DEFAULT_BASE_URL = "https://synap-cloud-prod.maximem.ai";
|
|
205
215
|
var DEFAULT_TIMEOUTS = { connect: 5, read: 30, write: 10 };
|
|
@@ -219,7 +229,7 @@ var HttpTransport = class {
|
|
|
219
229
|
this.#credentials = options.credentials;
|
|
220
230
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
221
231
|
this.timeouts = { ...DEFAULT_TIMEOUTS, ...options.timeouts };
|
|
222
|
-
this.retryPolicy = { ...DEFAULT_RETRY_POLICY, ...options.retryPolicy };
|
|
232
|
+
this.retryPolicy = options.retryPolicy === null ? { ...DEFAULT_RETRY_POLICY, maxAttempts: 1 } : { ...DEFAULT_RETRY_POLICY, ...options.retryPolicy };
|
|
223
233
|
this.userAgent = options.userAgent ?? "@maximem/synap-js-sdk";
|
|
224
234
|
this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
225
235
|
this.keepAliveOptions = options.keepAlive ?? {};
|
|
@@ -253,12 +263,38 @@ var HttpTransport = class {
|
|
|
253
263
|
updateCredentials(patch) {
|
|
254
264
|
this.#credentials = { ...this.#credentials, ...patch };
|
|
255
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* The instance's scoping mode, from whoami. Lives here rather than on the
|
|
268
|
+
* client because every interface factory already receives the transport,
|
|
269
|
+
* and it is per-client, so a module-level global would be wrong the moment a
|
|
270
|
+
* process holds two clients.
|
|
271
|
+
*
|
|
272
|
+
* undefined means the server did not report it, which is true of any
|
|
273
|
+
* deployment older than the field, and must mean "enforce nothing".
|
|
274
|
+
*/
|
|
275
|
+
userContextIsolation = void 0;
|
|
276
|
+
/** Refuse a customer_id on a B2C instance, at the call site. */
|
|
277
|
+
checkCustomerId(customerId, where) {
|
|
278
|
+
checkCustomerId(
|
|
279
|
+
this.userContextIsolation,
|
|
280
|
+
customerId,
|
|
281
|
+
where,
|
|
282
|
+
this.#credentials.instanceId
|
|
283
|
+
);
|
|
284
|
+
}
|
|
256
285
|
setTimeouts(timeouts) {
|
|
257
286
|
this.timeouts = { ...this.timeouts, ...timeouts };
|
|
258
287
|
}
|
|
259
|
-
/**
|
|
288
|
+
/**
|
|
289
|
+
* `null` DISABLES retries, matching Python.
|
|
290
|
+
*
|
|
291
|
+
* Python's `SDKConfig(retry_policy=None)` leaves `self.retry_policy` unset and
|
|
292
|
+
* `max_attempts` falls to 1 (http_client.py:180). We restored the default
|
|
293
|
+
* policy instead, so a caller following the "Disabling Retries" documentation
|
|
294
|
+
* got three attempts and no way to turn them off.
|
|
295
|
+
*/
|
|
260
296
|
setRetryPolicy(policy) {
|
|
261
|
-
this.retryPolicy = policy === null ? { ...DEFAULT_RETRY_POLICY } : { ...this.retryPolicy, ...policy };
|
|
297
|
+
this.retryPolicy = policy === null ? { ...DEFAULT_RETRY_POLICY, maxAttempts: 1 } : { ...this.retryPolicy, ...policy };
|
|
262
298
|
}
|
|
263
299
|
close() {
|
|
264
300
|
this.closed = true;
|
|
@@ -406,7 +442,9 @@ async function toHttpError(response, correlationId) {
|
|
|
406
442
|
return new InsufficientCreditsError(message, {
|
|
407
443
|
...opts,
|
|
408
444
|
balanceCredits: numberOrNull(parsed["balance_credits"]),
|
|
409
|
-
requiredCredits: numberOrNull(parsed["required_credits"])
|
|
445
|
+
requiredCredits: numberOrNull(parsed["minimum_required_credits"]) ?? numberOrNull(parsed["required_credits"]),
|
|
446
|
+
recoveryUrl: stringOrNull(parsed["recovery_url"]),
|
|
447
|
+
redeemUrl: stringOrNull(parsed["redeem_url"])
|
|
410
448
|
});
|
|
411
449
|
case 409:
|
|
412
450
|
if (detailObject !== null && detailObject["code"] === "transcript_conflict") {
|
|
@@ -433,6 +471,9 @@ async function toHttpError(response, correlationId) {
|
|
|
433
471
|
function numberOrNull(v) {
|
|
434
472
|
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
435
473
|
}
|
|
474
|
+
function stringOrNull(v) {
|
|
475
|
+
return typeof v === "string" && v !== "" ? v : null;
|
|
476
|
+
}
|
|
436
477
|
function withTimeout(ms, external) {
|
|
437
478
|
const controller = new AbortController();
|
|
438
479
|
const onExternalAbort = () => controller.abort(external?.reason);
|
|
@@ -507,9 +548,32 @@ function buildFetchRequest(scope, options) {
|
|
|
507
548
|
}
|
|
508
549
|
switch (scope) {
|
|
509
550
|
case "user":
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
551
|
+
{
|
|
552
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
553
|
+
body["user_id"] = userId;
|
|
554
|
+
const scopePath = opt(options, "scope_path", "scopePath");
|
|
555
|
+
if (scopePath !== void 0) body["scope"] = scopePath;
|
|
556
|
+
const contextMode = opt(options, "context_mode", "contextMode") ?? "in-conversation";
|
|
557
|
+
if (!VALID_CONTEXT_MODES.includes(contextMode)) {
|
|
558
|
+
throw new InvalidInputError(
|
|
559
|
+
`Invalid context_mode '${contextMode}'. Must be one of: ${VALID_CONTEXT_MODES.join(", ")}`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
const lastN = opt(options, "last_n_conversations", "lastNConversations") ?? 1;
|
|
563
|
+
if (!Number.isInteger(lastN)) {
|
|
564
|
+
throw new InvalidInputError("last_n_conversations must be an integer");
|
|
565
|
+
}
|
|
566
|
+
if (lastN < 0 || lastN > 20) {
|
|
567
|
+
throw new InvalidInputError("last_n_conversations must be between 0 and 20");
|
|
568
|
+
}
|
|
569
|
+
if (contextMode === "conversation-summary") {
|
|
570
|
+
body["context_mode"] = contextMode;
|
|
571
|
+
body["include_profile"] = opt(options, "include_profile", "includeProfile") ?? true;
|
|
572
|
+
body["last_n_conversations"] = lastN;
|
|
573
|
+
}
|
|
574
|
+
if (customerId !== void 0) body["customer_id"] = customerId;
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
513
577
|
break;
|
|
514
578
|
case "customer":
|
|
515
579
|
if (!customerId) throw new InvalidInputError("customer_id is required");
|
|
@@ -525,8 +589,13 @@ function buildFetchRequest(scope, options) {
|
|
|
525
589
|
}
|
|
526
590
|
return { endpoint: ENDPOINT_BY_SCOPE[scope], body };
|
|
527
591
|
}
|
|
592
|
+
var VALID_CONTEXT_MODES = ["in-conversation", "conversation-summary"];
|
|
528
593
|
async function fetchContext(transport, scope, options = {}, hooks = {}) {
|
|
529
594
|
const startedAt = Date.now();
|
|
595
|
+
transport.checkCustomerId(
|
|
596
|
+
opt(options, "customer_id", "customerId"),
|
|
597
|
+
`${scope}.context.fetch`
|
|
598
|
+
);
|
|
530
599
|
const anticipated = hooks.beforeFetch?.(scope, options) ?? null;
|
|
531
600
|
if (anticipated !== null) {
|
|
532
601
|
hooks.onServedFromCache?.(scope, options, anticipated);
|
|
@@ -787,6 +856,8 @@ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "error",
|
|
|
787
856
|
function createMemoriesInterface(transport) {
|
|
788
857
|
return {
|
|
789
858
|
async create(options) {
|
|
859
|
+
const o2 = options;
|
|
860
|
+
transport.checkCustomerId(o2["customer_id"] ?? o2["customerId"], "memories.create");
|
|
790
861
|
return transport.request("memories_create", { body: buildCreateBody(options) });
|
|
791
862
|
},
|
|
792
863
|
async batch_create(options) {
|
|
@@ -803,7 +874,7 @@ function createMemoriesInterface(transport) {
|
|
|
803
874
|
const userId = options.user_id ?? options.userId;
|
|
804
875
|
const customerId = options.customer_id ?? options.customerId;
|
|
805
876
|
if (!userId) throw new InvalidInputError("user_id is required");
|
|
806
|
-
|
|
877
|
+
transport.checkCustomerId(customerId, "memories.create_from_file");
|
|
807
878
|
const filePath = options.file_path ?? options.filePath;
|
|
808
879
|
const sources = [filePath, options.file, options.text].filter((v) => v !== void 0);
|
|
809
880
|
if (sources.length === 0) {
|
|
@@ -924,6 +995,15 @@ function validateInstanceId(instanceId) {
|
|
|
924
995
|
}
|
|
925
996
|
|
|
926
997
|
// src/conversation/interface.ts
|
|
998
|
+
var COMPACTION_LEVELS = [
|
|
999
|
+
"low",
|
|
1000
|
+
"medium",
|
|
1001
|
+
"high",
|
|
1002
|
+
"adaptive",
|
|
1003
|
+
"aggressive",
|
|
1004
|
+
"balanced",
|
|
1005
|
+
"conservative"
|
|
1006
|
+
];
|
|
927
1007
|
function either2(o2, snake, camel) {
|
|
928
1008
|
const a = o2[snake];
|
|
929
1009
|
if (a !== void 0) return a;
|
|
@@ -946,7 +1026,6 @@ function buildRecordMessageBody(options) {
|
|
|
946
1026
|
throw new InvalidInputError("content is required");
|
|
947
1027
|
}
|
|
948
1028
|
if (!userId) throw new InvalidInputError("user_id is required");
|
|
949
|
-
if (!customerId) throw new InvalidInputError("customer_id is required");
|
|
950
1029
|
const body = {
|
|
951
1030
|
conversation_id: conversationId,
|
|
952
1031
|
role: options.role,
|
|
@@ -1051,6 +1130,10 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
|
|
|
1051
1130
|
};
|
|
1052
1131
|
return {
|
|
1053
1132
|
async record_message(options) {
|
|
1133
|
+
transport.checkCustomerId(
|
|
1134
|
+
options["customer_id"] ?? options["customerId"],
|
|
1135
|
+
"conversation.record_message"
|
|
1136
|
+
);
|
|
1054
1137
|
const body = buildRecordMessageBody(options);
|
|
1055
1138
|
const result = await transport.request("conversations_messages", { body });
|
|
1056
1139
|
onTurn({
|
|
@@ -1069,6 +1152,13 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
|
|
|
1069
1152
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
1070
1153
|
throw new InvalidInputError("messages must be a non-empty array");
|
|
1071
1154
|
}
|
|
1155
|
+
for (const m of messages) {
|
|
1156
|
+
const mo = m;
|
|
1157
|
+
transport.checkCustomerId(
|
|
1158
|
+
mo["customer_id"] ?? mo["customerId"],
|
|
1159
|
+
"conversation.record_messages_batch"
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1072
1162
|
const bodies = messages.map(buildRecordMessageBody);
|
|
1073
1163
|
const result = await transport.request("conversations_messages_batch", {
|
|
1074
1164
|
body: { messages: bodies }
|
|
@@ -1184,15 +1274,26 @@ function createUserNamespace(transport, hooks = {}) {
|
|
|
1184
1274
|
}
|
|
1185
1275
|
|
|
1186
1276
|
// src/credits/interface.ts
|
|
1277
|
+
function iso2(v) {
|
|
1278
|
+
return v instanceof Date ? v.toISOString() : v;
|
|
1279
|
+
}
|
|
1187
1280
|
function createCreditsNamespace(transport) {
|
|
1188
1281
|
return {
|
|
1189
1282
|
async get_balance() {
|
|
1190
1283
|
return transport.request("credits_balance");
|
|
1191
1284
|
},
|
|
1192
1285
|
async get_ledger(options = {}) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1286
|
+
const query = {
|
|
1287
|
+
limit: options.limit ?? 100,
|
|
1288
|
+
offset: options.offset ?? 0
|
|
1289
|
+
};
|
|
1290
|
+
const entryType = options.entry_type ?? options.entryType;
|
|
1291
|
+
if (entryType !== void 0) query["entry_type"] = entryType;
|
|
1292
|
+
const from = options.from_time ?? options.fromTime;
|
|
1293
|
+
if (from !== void 0) query["from"] = iso2(from);
|
|
1294
|
+
const to = options.to_time ?? options.toTime;
|
|
1295
|
+
if (to !== void 0) query["to"] = iso2(to);
|
|
1296
|
+
return transport.request("credits_ledger", { query });
|
|
1196
1297
|
},
|
|
1197
1298
|
async estimate(options) {
|
|
1198
1299
|
return transport.request("credits_estimate", {
|
|
@@ -1272,7 +1373,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1272
1373
|
if (client !== null) {
|
|
1273
1374
|
throw new ListeningAlreadyActiveError("Listening is already active on this instance");
|
|
1274
1375
|
}
|
|
1275
|
-
const { GrpcStreamClient } = await import("./stream-client-
|
|
1376
|
+
const { GrpcStreamClient } = await import("./stream-client-6V4FGKZ3.js");
|
|
1276
1377
|
const onContext = options.on_context ?? options.onContext;
|
|
1277
1378
|
const onDisconnect = options.on_disconnect ?? options.onDisconnect;
|
|
1278
1379
|
const onReconnect = options.on_reconnect ?? options.onReconnect;
|
|
@@ -1332,6 +1433,12 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1332
1433
|
session_id: options.session_id ?? options.sessionId ?? "",
|
|
1333
1434
|
metadata: options.metadata ?? {},
|
|
1334
1435
|
timestamp_ms: Date.now(),
|
|
1436
|
+
tool_name: options.tool_name ?? options.toolName ?? "",
|
|
1437
|
+
// Python serialises tool_args with json.dumps into tool_args_json.
|
|
1438
|
+
tool_args_json: (() => {
|
|
1439
|
+
const a = options.tool_args ?? options.toolArgs;
|
|
1440
|
+
return a === void 0 ? "" : JSON.stringify(a);
|
|
1441
|
+
})(),
|
|
1335
1442
|
search_queries: options.search_queries ?? options.searchQueries ?? [],
|
|
1336
1443
|
context_types: options.context_types ?? options.contextTypes ?? []
|
|
1337
1444
|
});
|
|
@@ -2377,6 +2484,8 @@ var SynapClient = class {
|
|
|
2377
2484
|
const whoami = await this.#transport.request("whoami", {});
|
|
2378
2485
|
const resolvedClientId = String(whoami["client_id"] ?? "");
|
|
2379
2486
|
const resolvedInstanceId = String(whoami["instance_id"] ?? "");
|
|
2487
|
+
const iso3 = whoami["user_context_isolation"];
|
|
2488
|
+
this.#transport.userContextIsolation = typeof iso3 === "string" ? iso3 : void 0;
|
|
2380
2489
|
this.#transport.updateCredentials({
|
|
2381
2490
|
...resolvedClientId !== "" && credentials.clientId === "" ? { clientId: resolvedClientId } : {},
|
|
2382
2491
|
...resolvedInstanceId !== "" && credentials.instanceId === "" ? { instanceId: resolvedInstanceId } : {}
|
|
@@ -2665,6 +2774,7 @@ export {
|
|
|
2665
2774
|
AnticipationCache,
|
|
2666
2775
|
AuthenticationError,
|
|
2667
2776
|
BM25,
|
|
2777
|
+
COMPACTION_LEVELS,
|
|
2668
2778
|
CONTRACT,
|
|
2669
2779
|
ConflictError,
|
|
2670
2780
|
ConnectionError,
|