@maximem/synap-js-sdk 0.4.3 → 0.4.5
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/anticipation-cache-ARYEEKMU.cjs.map +1 -1
- 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-3JSE64GW.cjs.map +1 -0
- package/dist/{chunk-T3NZBIV2.js → chunk-OCMM2Z5P.js} +7 -1
- package/dist/chunk-OCMM2Z5P.js.map +1 -0
- package/dist/chunk-RB57I2IQ.cjs.map +1 -1
- package/dist/chunk-ZKOJDUN4.cjs.map +1 -1
- package/dist/{chunk-5SC3ELNK.js → chunk-ZU6ZC4SU.js} +1 -1
- package/dist/chunk-ZU6ZC4SU.js.map +1 -0
- package/dist/env-55ULHNJZ.cjs.map +1 -1
- package/dist/grpc/index.cjs +7 -7
- package/dist/grpc/index.cjs.map +1 -1
- 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 +402 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +234 -27
- package/dist/index.d.ts +234 -27
- package/dist/index.js +317 -30
- 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/chunk-VXRCBMCY.cjs.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,
|
|
@@ -212,7 +212,12 @@ function checkCustomerId(isolation, customerId, where, instanceId) {
|
|
|
212
212
|
|
|
213
213
|
// src/transport/http.ts
|
|
214
214
|
var DEFAULT_BASE_URL = "https://synap-cloud-prod.maximem.ai";
|
|
215
|
-
var DEFAULT_TIMEOUTS = {
|
|
215
|
+
var DEFAULT_TIMEOUTS = {
|
|
216
|
+
connect: 5,
|
|
217
|
+
read: 30,
|
|
218
|
+
write: 10,
|
|
219
|
+
streamIdle: 60
|
|
220
|
+
};
|
|
216
221
|
var HttpTransport = class {
|
|
217
222
|
baseUrl;
|
|
218
223
|
// Mutable so `configure()` can adjust them before the first request, and so
|
|
@@ -229,7 +234,7 @@ var HttpTransport = class {
|
|
|
229
234
|
this.#credentials = options.credentials;
|
|
230
235
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
231
236
|
this.timeouts = { ...DEFAULT_TIMEOUTS, ...options.timeouts };
|
|
232
|
-
this.retryPolicy = { ...DEFAULT_RETRY_POLICY, ...options.retryPolicy };
|
|
237
|
+
this.retryPolicy = options.retryPolicy === null ? { ...DEFAULT_RETRY_POLICY, maxAttempts: 1 } : { ...DEFAULT_RETRY_POLICY, ...options.retryPolicy };
|
|
233
238
|
this.userAgent = options.userAgent ?? "@maximem/synap-js-sdk";
|
|
234
239
|
this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
235
240
|
this.keepAliveOptions = options.keepAlive ?? {};
|
|
@@ -285,9 +290,16 @@ var HttpTransport = class {
|
|
|
285
290
|
setTimeouts(timeouts) {
|
|
286
291
|
this.timeouts = { ...this.timeouts, ...timeouts };
|
|
287
292
|
}
|
|
288
|
-
/**
|
|
293
|
+
/**
|
|
294
|
+
* `null` DISABLES retries, matching Python.
|
|
295
|
+
*
|
|
296
|
+
* Python's `SDKConfig(retry_policy=None)` leaves `self.retry_policy` unset and
|
|
297
|
+
* `max_attempts` falls to 1 (http_client.py:180). We restored the default
|
|
298
|
+
* policy instead, so a caller following the "Disabling Retries" documentation
|
|
299
|
+
* got three attempts and no way to turn them off.
|
|
300
|
+
*/
|
|
289
301
|
setRetryPolicy(policy) {
|
|
290
|
-
this.retryPolicy = policy === null ? { ...DEFAULT_RETRY_POLICY } : { ...this.retryPolicy, ...policy };
|
|
302
|
+
this.retryPolicy = policy === null ? { ...DEFAULT_RETRY_POLICY, maxAttempts: 1 } : { ...this.retryPolicy, ...policy };
|
|
291
303
|
}
|
|
292
304
|
close() {
|
|
293
305
|
this.closed = true;
|
|
@@ -435,7 +447,9 @@ async function toHttpError(response, correlationId) {
|
|
|
435
447
|
return new InsufficientCreditsError(message, {
|
|
436
448
|
...opts,
|
|
437
449
|
balanceCredits: numberOrNull(parsed["balance_credits"]),
|
|
438
|
-
requiredCredits: numberOrNull(parsed["required_credits"])
|
|
450
|
+
requiredCredits: numberOrNull(parsed["minimum_required_credits"]) ?? numberOrNull(parsed["required_credits"]),
|
|
451
|
+
recoveryUrl: stringOrNull(parsed["recovery_url"]),
|
|
452
|
+
redeemUrl: stringOrNull(parsed["redeem_url"])
|
|
439
453
|
});
|
|
440
454
|
case 409:
|
|
441
455
|
if (detailObject !== null && detailObject["code"] === "transcript_conflict") {
|
|
@@ -462,6 +476,9 @@ async function toHttpError(response, correlationId) {
|
|
|
462
476
|
function numberOrNull(v) {
|
|
463
477
|
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
464
478
|
}
|
|
479
|
+
function stringOrNull(v) {
|
|
480
|
+
return typeof v === "string" && v !== "" ? v : null;
|
|
481
|
+
}
|
|
465
482
|
function withTimeout(ms, external) {
|
|
466
483
|
const controller = new AbortController();
|
|
467
484
|
const onExternalAbort = () => controller.abort(external?.reason);
|
|
@@ -536,9 +553,32 @@ function buildFetchRequest(scope, options) {
|
|
|
536
553
|
}
|
|
537
554
|
switch (scope) {
|
|
538
555
|
case "user":
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
556
|
+
{
|
|
557
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
558
|
+
body["user_id"] = userId;
|
|
559
|
+
const scopePath = opt(options, "scope_path", "scopePath");
|
|
560
|
+
if (scopePath !== void 0) body["scope"] = scopePath;
|
|
561
|
+
const contextMode = opt(options, "context_mode", "contextMode") ?? "in-conversation";
|
|
562
|
+
if (!VALID_CONTEXT_MODES.includes(contextMode)) {
|
|
563
|
+
throw new InvalidInputError(
|
|
564
|
+
`Invalid context_mode '${contextMode}'. Must be one of: ${VALID_CONTEXT_MODES.join(", ")}`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
const lastN = opt(options, "last_n_conversations", "lastNConversations") ?? 1;
|
|
568
|
+
if (!Number.isInteger(lastN)) {
|
|
569
|
+
throw new InvalidInputError("last_n_conversations must be an integer");
|
|
570
|
+
}
|
|
571
|
+
if (lastN < 0 || lastN > 20) {
|
|
572
|
+
throw new InvalidInputError("last_n_conversations must be between 0 and 20");
|
|
573
|
+
}
|
|
574
|
+
if (contextMode === "conversation-summary") {
|
|
575
|
+
body["context_mode"] = contextMode;
|
|
576
|
+
body["include_profile"] = opt(options, "include_profile", "includeProfile") ?? true;
|
|
577
|
+
body["last_n_conversations"] = lastN;
|
|
578
|
+
}
|
|
579
|
+
if (customerId !== void 0) body["customer_id"] = customerId;
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
542
582
|
break;
|
|
543
583
|
case "customer":
|
|
544
584
|
if (!customerId) throw new InvalidInputError("customer_id is required");
|
|
@@ -554,6 +594,7 @@ function buildFetchRequest(scope, options) {
|
|
|
554
594
|
}
|
|
555
595
|
return { endpoint: ENDPOINT_BY_SCOPE[scope], body };
|
|
556
596
|
}
|
|
597
|
+
var VALID_CONTEXT_MODES = ["in-conversation", "conversation-summary"];
|
|
557
598
|
async function fetchContext(transport, scope, options = {}, hooks = {}) {
|
|
558
599
|
const startedAt = Date.now();
|
|
559
600
|
transport.checkCustomerId(
|
|
@@ -959,6 +1000,15 @@ function validateInstanceId(instanceId) {
|
|
|
959
1000
|
}
|
|
960
1001
|
|
|
961
1002
|
// src/conversation/interface.ts
|
|
1003
|
+
var COMPACTION_LEVELS = [
|
|
1004
|
+
"low",
|
|
1005
|
+
"medium",
|
|
1006
|
+
"high",
|
|
1007
|
+
"adaptive",
|
|
1008
|
+
"aggressive",
|
|
1009
|
+
"balanced",
|
|
1010
|
+
"conservative"
|
|
1011
|
+
];
|
|
962
1012
|
function either2(o2, snake, camel) {
|
|
963
1013
|
const a = o2[snake];
|
|
964
1014
|
if (a !== void 0) return a;
|
|
@@ -1076,7 +1126,7 @@ var CompactionSubscribers = class {
|
|
|
1076
1126
|
function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
|
|
1077
1127
|
}, onTurn = () => {
|
|
1078
1128
|
}, onFetched = () => {
|
|
1079
|
-
}) {
|
|
1129
|
+
}, localShortTerm = { prompt: () => null, compacted: () => null }) {
|
|
1080
1130
|
const convId = (v) => {
|
|
1081
1131
|
const id = typeof v === "string" ? v : v.conversation_id ?? v.conversationId;
|
|
1082
1132
|
if (!id) throw new InvalidInputError("conversation_id is required");
|
|
@@ -1157,6 +1207,8 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
|
|
|
1157
1207
|
},
|
|
1158
1208
|
get_context_for_prompt: async (options = {}) => {
|
|
1159
1209
|
const id = convId(options);
|
|
1210
|
+
const local = localShortTerm.prompt(id, options.style ?? "structured");
|
|
1211
|
+
if (local !== null) return local;
|
|
1160
1212
|
const result = await transport.request("context_for_prompt", {
|
|
1161
1213
|
pathParams: { conversation_id: id },
|
|
1162
1214
|
query: { style: options.style ?? "structured" }
|
|
@@ -1168,6 +1220,10 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
|
|
|
1168
1220
|
},
|
|
1169
1221
|
get_compacted: async (options) => {
|
|
1170
1222
|
const id = convId(options);
|
|
1223
|
+
if (options.version === void 0) {
|
|
1224
|
+
const local = localShortTerm.compacted(id, options.format ?? "structured");
|
|
1225
|
+
if (local !== null) return local;
|
|
1226
|
+
}
|
|
1171
1227
|
const query = { format: options.format ?? "structured" };
|
|
1172
1228
|
if (options.version !== void 0) query["version"] = options.version;
|
|
1173
1229
|
try {
|
|
@@ -1229,15 +1285,26 @@ function createUserNamespace(transport, hooks = {}) {
|
|
|
1229
1285
|
}
|
|
1230
1286
|
|
|
1231
1287
|
// src/credits/interface.ts
|
|
1288
|
+
function iso2(v) {
|
|
1289
|
+
return v instanceof Date ? v.toISOString() : v;
|
|
1290
|
+
}
|
|
1232
1291
|
function createCreditsNamespace(transport) {
|
|
1233
1292
|
return {
|
|
1234
1293
|
async get_balance() {
|
|
1235
1294
|
return transport.request("credits_balance");
|
|
1236
1295
|
},
|
|
1237
1296
|
async get_ledger(options = {}) {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1297
|
+
const query = {
|
|
1298
|
+
limit: options.limit ?? 100,
|
|
1299
|
+
offset: options.offset ?? 0
|
|
1300
|
+
};
|
|
1301
|
+
const entryType = options.entry_type ?? options.entryType;
|
|
1302
|
+
if (entryType !== void 0) query["entry_type"] = entryType;
|
|
1303
|
+
const from = options.from_time ?? options.fromTime;
|
|
1304
|
+
if (from !== void 0) query["from"] = iso2(from);
|
|
1305
|
+
const to = options.to_time ?? options.toTime;
|
|
1306
|
+
if (to !== void 0) query["to"] = iso2(to);
|
|
1307
|
+
return transport.request("credits_ledger", { query });
|
|
1241
1308
|
},
|
|
1242
1309
|
async estimate(options) {
|
|
1243
1310
|
return transport.request("credits_estimate", {
|
|
@@ -1287,6 +1354,18 @@ function envTls() {
|
|
|
1287
1354
|
if (raw === void 0 || raw === "") return void 0;
|
|
1288
1355
|
return !["0", "false", "no", "off"].includes(raw.trim().toLowerCase());
|
|
1289
1356
|
}
|
|
1357
|
+
function thinkingMetadata(options) {
|
|
1358
|
+
const md = { ...options.metadata ?? {} };
|
|
1359
|
+
const step = options.step_index ?? options.stepIndex;
|
|
1360
|
+
if (step !== void 0 && md["step_index"] === void 0) {
|
|
1361
|
+
md["step_index"] = String(step);
|
|
1362
|
+
}
|
|
1363
|
+
const kind = options.thought_type ?? options.thoughtType;
|
|
1364
|
+
if (kind && md["thought_type"] === void 0) {
|
|
1365
|
+
md["thought_type"] = kind;
|
|
1366
|
+
}
|
|
1367
|
+
return md;
|
|
1368
|
+
}
|
|
1290
1369
|
function createInstanceNamespace(credentials, cache, onTurn) {
|
|
1291
1370
|
let client = null;
|
|
1292
1371
|
let dispatchCompaction = null;
|
|
@@ -1317,7 +1396,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1317
1396
|
if (client !== null) {
|
|
1318
1397
|
throw new ListeningAlreadyActiveError("Listening is already active on this instance");
|
|
1319
1398
|
}
|
|
1320
|
-
const { GrpcStreamClient } = await import("./stream-client-
|
|
1399
|
+
const { GrpcStreamClient } = await import("./stream-client-6V4FGKZ3.js");
|
|
1321
1400
|
const onContext = options.on_context ?? options.onContext;
|
|
1322
1401
|
const onDisconnect = options.on_disconnect ?? options.onDisconnect;
|
|
1323
1402
|
const onReconnect = options.on_reconnect ?? options.onReconnect;
|
|
@@ -1377,6 +1456,12 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1377
1456
|
session_id: options.session_id ?? options.sessionId ?? "",
|
|
1378
1457
|
metadata: options.metadata ?? {},
|
|
1379
1458
|
timestamp_ms: Date.now(),
|
|
1459
|
+
tool_name: options.tool_name ?? options.toolName ?? "",
|
|
1460
|
+
// Python serialises tool_args with json.dumps into tool_args_json.
|
|
1461
|
+
tool_args_json: (() => {
|
|
1462
|
+
const a = options.tool_args ?? options.toolArgs;
|
|
1463
|
+
return a === void 0 ? "" : JSON.stringify(a);
|
|
1464
|
+
})(),
|
|
1380
1465
|
search_queries: options.search_queries ?? options.searchQueries ?? [],
|
|
1381
1466
|
context_types: options.context_types ?? options.contextTypes ?? []
|
|
1382
1467
|
});
|
|
@@ -1392,7 +1477,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1392
1477
|
user_id: options.user_id ?? options.userId ?? "",
|
|
1393
1478
|
customer_id: options.customer_id ?? options.customerId ?? "",
|
|
1394
1479
|
session_id: options.session_id ?? options.sessionId ?? "",
|
|
1395
|
-
metadata: options
|
|
1480
|
+
metadata: thinkingMetadata(options),
|
|
1396
1481
|
timestamp_ms: Date.now()
|
|
1397
1482
|
});
|
|
1398
1483
|
},
|
|
@@ -1575,11 +1660,11 @@ var ShortTermStore = class {
|
|
|
1575
1660
|
// src/context/overlay.ts
|
|
1576
1661
|
var MAX_TURNS = 20;
|
|
1577
1662
|
var TOKEN_BUDGET = 2e3;
|
|
1578
|
-
function verbatimOverlayEnabled() {
|
|
1663
|
+
function verbatimOverlayEnabled(configured) {
|
|
1579
1664
|
const raw = (getEnv("SYNAP_ST_VERBATIM_OVERLAY") ?? "").trim().toLowerCase();
|
|
1580
1665
|
if (["0", "false", "no", "off"].includes(raw)) return false;
|
|
1581
1666
|
if (["1", "true", "yes", "on"].includes(raw)) return true;
|
|
1582
|
-
return true;
|
|
1667
|
+
return configured ?? true;
|
|
1583
1668
|
}
|
|
1584
1669
|
function estimateTokens(turn) {
|
|
1585
1670
|
return Math.max(1, Math.floor(String(turn.content ?? "").length / 4));
|
|
@@ -1593,13 +1678,13 @@ function budgetRecentTurns(turns) {
|
|
|
1593
1678
|
}
|
|
1594
1679
|
return windowed;
|
|
1595
1680
|
}
|
|
1596
|
-
function overlayLocalRecentTurns(response, store, conversationId) {
|
|
1681
|
+
function overlayLocalRecentTurns(response, store, conversationId, configured) {
|
|
1597
1682
|
if (conversationId === void 0 || conversationId === "") return;
|
|
1598
1683
|
try {
|
|
1599
1684
|
const entry = store.get(conversationId);
|
|
1600
1685
|
if (entry === null) return;
|
|
1601
1686
|
const existing = response.conversation_context ?? null;
|
|
1602
|
-
if (existing !== null && !verbatimOverlayEnabled()) return;
|
|
1687
|
+
if (existing !== null && !verbatimOverlayEnabled(configured)) return;
|
|
1603
1688
|
const localTurns = budgetRecentTurns(entry.recentTurns);
|
|
1604
1689
|
const serverField = (name) => existing === null ? void 0 : existing[name];
|
|
1605
1690
|
const orLocal = (name, local) => {
|
|
@@ -1621,6 +1706,158 @@ function overlayLocalRecentTurns(response, store, conversationId) {
|
|
|
1621
1706
|
}
|
|
1622
1707
|
}
|
|
1623
1708
|
|
|
1709
|
+
// src/context/local-prompt.ts
|
|
1710
|
+
var SUPPORTED_STYLES = ["structured", "narrative", "bullet_points"];
|
|
1711
|
+
var DEFAULT_STYLE = "structured";
|
|
1712
|
+
function stringify(value) {
|
|
1713
|
+
if (value === null || value === void 0) return "";
|
|
1714
|
+
const t = typeof value;
|
|
1715
|
+
if (t === "string" || t === "number" || t === "boolean") return String(value);
|
|
1716
|
+
try {
|
|
1717
|
+
return JSON.stringify(value) ?? "";
|
|
1718
|
+
} catch {
|
|
1719
|
+
return String(value);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
function asListOfObjects(value) {
|
|
1723
|
+
if (!value) return [];
|
|
1724
|
+
if (Array.isArray(value)) {
|
|
1725
|
+
const out = [];
|
|
1726
|
+
for (const v of value) {
|
|
1727
|
+
if (v && typeof v === "object" && !Array.isArray(v)) out.push(v);
|
|
1728
|
+
else if (typeof v === "string") out.push({ content: v });
|
|
1729
|
+
}
|
|
1730
|
+
return out;
|
|
1731
|
+
}
|
|
1732
|
+
if (typeof value === "object") return [value];
|
|
1733
|
+
return [];
|
|
1734
|
+
}
|
|
1735
|
+
function itemText(item) {
|
|
1736
|
+
return item["content"] || item["text"] || stringify(item);
|
|
1737
|
+
}
|
|
1738
|
+
function buildRecentMessages(turns) {
|
|
1739
|
+
return (turns ?? []).map((t, idx) => ({
|
|
1740
|
+
role: t.role || "user",
|
|
1741
|
+
content: t.content ?? "",
|
|
1742
|
+
timestamp: t.timestamp,
|
|
1743
|
+
message_id: `local-${idx}`
|
|
1744
|
+
}));
|
|
1745
|
+
}
|
|
1746
|
+
function compactedMessageCount(_entry) {
|
|
1747
|
+
return 0;
|
|
1748
|
+
}
|
|
1749
|
+
function formatStructured(entry, recent) {
|
|
1750
|
+
const parts = [];
|
|
1751
|
+
if (entry.summary) {
|
|
1752
|
+
parts.push("## Summary");
|
|
1753
|
+
parts.push(entry.summary.trim());
|
|
1754
|
+
}
|
|
1755
|
+
if (entry.factualParagraph) {
|
|
1756
|
+
parts.push("## Facts");
|
|
1757
|
+
parts.push(entry.factualParagraph.trim());
|
|
1758
|
+
}
|
|
1759
|
+
if (entry.conversationalParagraph) {
|
|
1760
|
+
parts.push("## Conversation");
|
|
1761
|
+
parts.push(entry.conversationalParagraph.trim());
|
|
1762
|
+
}
|
|
1763
|
+
const cs = entry.currentState ?? {};
|
|
1764
|
+
if (Object.keys(cs).length > 0) {
|
|
1765
|
+
parts.push("## Current State");
|
|
1766
|
+
for (const [k, v] of Object.entries(cs)) parts.push(`- ${k}: ${stringify(v)}`);
|
|
1767
|
+
}
|
|
1768
|
+
const ke = entry.keyExtractions ?? {};
|
|
1769
|
+
if (Object.keys(ke).length > 0) {
|
|
1770
|
+
parts.push("## Key Extractions");
|
|
1771
|
+
for (const [cat, items] of Object.entries(ke)) {
|
|
1772
|
+
if (!items) continue;
|
|
1773
|
+
parts.push(`### ${cat}`);
|
|
1774
|
+
for (const it of asListOfObjects(items)) parts.push(`- ${itemText(it)}`);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
if (recent.length > 0) {
|
|
1778
|
+
parts.push(`## Recent Turns (${recent.length})`);
|
|
1779
|
+
for (const m of recent) parts.push(`**${m.role}**: ${m.content}`);
|
|
1780
|
+
}
|
|
1781
|
+
return parts.filter(Boolean).join("\n\n").trim();
|
|
1782
|
+
}
|
|
1783
|
+
function formatNarrative(entry, recent) {
|
|
1784
|
+
const parts = [];
|
|
1785
|
+
if (entry.summary) parts.push(entry.summary.trim());
|
|
1786
|
+
if (entry.conversationalParagraph) parts.push(entry.conversationalParagraph.trim());
|
|
1787
|
+
else if (entry.factualParagraph) parts.push(entry.factualParagraph.trim());
|
|
1788
|
+
if (recent.length > 0) {
|
|
1789
|
+
const tail = recent.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
1790
|
+
parts.push(`Recent exchanges:
|
|
1791
|
+
${tail}`);
|
|
1792
|
+
}
|
|
1793
|
+
return parts.filter(Boolean).join("\n\n").trim();
|
|
1794
|
+
}
|
|
1795
|
+
function formatBullets(entry, recent) {
|
|
1796
|
+
const parts = [];
|
|
1797
|
+
if (entry.summary) parts.push(`- Summary: ${entry.summary.trim()}`);
|
|
1798
|
+
for (const [k, v] of Object.entries(entry.currentState ?? {})) {
|
|
1799
|
+
parts.push(`- ${k}: ${stringify(v)}`);
|
|
1800
|
+
}
|
|
1801
|
+
for (const [cat, items] of Object.entries(entry.keyExtractions ?? {})) {
|
|
1802
|
+
for (const it of asListOfObjects(items)) parts.push(`- (${cat}) ${itemText(it)}`);
|
|
1803
|
+
}
|
|
1804
|
+
for (const m of recent) parts.push(`- ${m.role}: ${m.content}`);
|
|
1805
|
+
return parts.join("\n").trim();
|
|
1806
|
+
}
|
|
1807
|
+
function formatBlock(entry, recent, style) {
|
|
1808
|
+
if (style === "narrative") return formatNarrative(entry, recent);
|
|
1809
|
+
if (style === "bullet_points") return formatBullets(entry, recent);
|
|
1810
|
+
return formatStructured(entry, recent);
|
|
1811
|
+
}
|
|
1812
|
+
function renderForPrompt(entry, style = DEFAULT_STYLE, now = Date.now) {
|
|
1813
|
+
const chosen = SUPPORTED_STYLES.includes(style) ? style : DEFAULT_STYLE;
|
|
1814
|
+
const recent = buildRecentMessages(entry.recentTurns);
|
|
1815
|
+
const formatted = formatBlock(entry, recent, chosen);
|
|
1816
|
+
const available = entry.compactionId !== null || entry.recentTurns.length > 0;
|
|
1817
|
+
let compactionAge = null;
|
|
1818
|
+
if (entry.compactedAt !== null) {
|
|
1819
|
+
const parsed = Date.parse(entry.compactedAt);
|
|
1820
|
+
if (!Number.isNaN(parsed)) compactionAge = Math.floor((now() - parsed) / 1e3);
|
|
1821
|
+
}
|
|
1822
|
+
const compacted = compactedMessageCount(entry);
|
|
1823
|
+
return {
|
|
1824
|
+
formatted_context: available ? formatted : null,
|
|
1825
|
+
available,
|
|
1826
|
+
is_stale: false,
|
|
1827
|
+
compression_ratio: null,
|
|
1828
|
+
validation_score: null,
|
|
1829
|
+
compaction_age_seconds: compactionAge,
|
|
1830
|
+
quality_warning: false,
|
|
1831
|
+
recent_messages: recent,
|
|
1832
|
+
recent_message_count: recent.length,
|
|
1833
|
+
compacted_message_count: compacted,
|
|
1834
|
+
total_message_count: recent.length + compacted
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
function renderCompacted(entry, format = "structured", correlationId = "") {
|
|
1838
|
+
if (entry.compactionId === null) return null;
|
|
1839
|
+
const formatted = formatBlock(
|
|
1840
|
+
entry,
|
|
1841
|
+
buildRecentMessages(entry.recentTurns),
|
|
1842
|
+
format === "narrative" ? "narrative" : "structured"
|
|
1843
|
+
);
|
|
1844
|
+
return {
|
|
1845
|
+
compacted_context: formatted,
|
|
1846
|
+
compaction_id: entry.compactionId,
|
|
1847
|
+
compacted_at: entry.compactedAt,
|
|
1848
|
+
conversation_id: entry.conversationId,
|
|
1849
|
+
current_state: entry.currentState,
|
|
1850
|
+
correlation_id: correlationId,
|
|
1851
|
+
original_token_count: null,
|
|
1852
|
+
compacted_token_count: null,
|
|
1853
|
+
compression_ratio: null,
|
|
1854
|
+
level_applied: null,
|
|
1855
|
+
validation_score: null,
|
|
1856
|
+
validation_passed: null,
|
|
1857
|
+
quality_warning: false
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1624
1861
|
// src/context/user-summary.ts
|
|
1625
1862
|
var USER_SUMMARY_INTERVAL = 5;
|
|
1626
1863
|
var MAX_SUMMARY_ITEMS = 3;
|
|
@@ -2209,6 +2446,9 @@ var SynapClient = class {
|
|
|
2209
2446
|
#registryKeys;
|
|
2210
2447
|
#compactionSubscribers;
|
|
2211
2448
|
#shortTerm;
|
|
2449
|
+
#stVerbatimOverlay;
|
|
2450
|
+
#logger;
|
|
2451
|
+
#stAuthoritative;
|
|
2212
2452
|
#turns;
|
|
2213
2453
|
#fetchHooks;
|
|
2214
2454
|
constructor(options = {}) {
|
|
@@ -2279,6 +2519,9 @@ var SynapClient = class {
|
|
|
2279
2519
|
this.client = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "client", o2, this.#fetchHooks) } };
|
|
2280
2520
|
this.#compactionSubscribers = new CompactionSubscribers();
|
|
2281
2521
|
this.#shortTerm = new ShortTermStore();
|
|
2522
|
+
this.#stVerbatimOverlay = options.st_verbatim_overlay ?? options.stVerbatimOverlay;
|
|
2523
|
+
this.#logger = options.logger;
|
|
2524
|
+
this.#stAuthoritative = options.sdk_st_authoritative ?? options.sdkStAuthoritative;
|
|
2282
2525
|
this.#turns = new TurnCounter();
|
|
2283
2526
|
this.conversation = createConversationNamespace(
|
|
2284
2527
|
this.#transport,
|
|
@@ -2299,8 +2542,12 @@ var SynapClient = class {
|
|
|
2299
2542
|
this.#shortTerm.appendTurn(conversationId, role, content, timestamp);
|
|
2300
2543
|
},
|
|
2301
2544
|
(response, conversationId, userId) => {
|
|
2302
|
-
overlayLocalRecentTurns(response, this.#shortTerm, conversationId);
|
|
2545
|
+
overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
|
|
2303
2546
|
this.#injectUserSummary(response, conversationId, userId);
|
|
2547
|
+
},
|
|
2548
|
+
{
|
|
2549
|
+
prompt: (conversationId, style) => this.#localPrompt(conversationId, style),
|
|
2550
|
+
compacted: (conversationId, format) => this.#localCompacted(conversationId, format)
|
|
2304
2551
|
}
|
|
2305
2552
|
);
|
|
2306
2553
|
this.memories = createMemoriesInterface(this.#transport);
|
|
@@ -2422,8 +2669,8 @@ var SynapClient = class {
|
|
|
2422
2669
|
const whoami = await this.#transport.request("whoami", {});
|
|
2423
2670
|
const resolvedClientId = String(whoami["client_id"] ?? "");
|
|
2424
2671
|
const resolvedInstanceId = String(whoami["instance_id"] ?? "");
|
|
2425
|
-
const
|
|
2426
|
-
this.#transport.userContextIsolation = typeof
|
|
2672
|
+
const iso3 = whoami["user_context_isolation"];
|
|
2673
|
+
this.#transport.userContextIsolation = typeof iso3 === "string" ? iso3 : void 0;
|
|
2427
2674
|
this.#transport.updateCredentials({
|
|
2428
2675
|
...resolvedClientId !== "" && credentials.clientId === "" ? { clientId: resolvedClientId } : {},
|
|
2429
2676
|
...resolvedInstanceId !== "" && credentials.instanceId === "" ? { instanceId: resolvedInstanceId } : {}
|
|
@@ -2459,11 +2706,12 @@ var SynapClient = class {
|
|
|
2459
2706
|
* initialization: the transport and cache are already built by then and a
|
|
2460
2707
|
* half-applied config is worse than a rejected one.
|
|
2461
2708
|
*
|
|
2462
|
-
*
|
|
2463
|
-
* `
|
|
2464
|
-
*
|
|
2465
|
-
*
|
|
2466
|
-
*
|
|
2709
|
+
* `logger` is honoured here as well as on the constructor. The remaining
|
|
2710
|
+
* Python-only keys (`storage_path`, `cache_backend`,
|
|
2711
|
+
* `session_timeout_minutes`, `log_level`) are accepted and ignored rather
|
|
2712
|
+
* than rejected, because this SDK has no SQLite cache backend and no global
|
|
2713
|
+
* logging framework whose level there would be to set. Ignoring them keeps
|
|
2714
|
+
* a shared config object portable between the two SDKs.
|
|
2467
2715
|
*/
|
|
2468
2716
|
configure(options = {}) {
|
|
2469
2717
|
if (this.#initialized) {
|
|
@@ -2473,6 +2721,7 @@ var SynapClient = class {
|
|
|
2473
2721
|
if (options.retryPolicy !== void 0 || options.retry_policy !== void 0) {
|
|
2474
2722
|
this.#transport.setRetryPolicy(options.retryPolicy ?? options.retry_policy ?? null);
|
|
2475
2723
|
}
|
|
2724
|
+
if (options.logger !== void 0) this.#logger = options.logger;
|
|
2476
2725
|
}
|
|
2477
2726
|
/**
|
|
2478
2727
|
* Fetch and merge context across every scope with an identifier, in one call.
|
|
@@ -2570,7 +2819,7 @@ var SynapClient = class {
|
|
|
2570
2819
|
}
|
|
2571
2820
|
const merged = mergeScopeResults(successful);
|
|
2572
2821
|
for (const [, response] of successful) {
|
|
2573
|
-
overlayLocalRecentTurns(response, this.#shortTerm, conversationId);
|
|
2822
|
+
overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
|
|
2574
2823
|
}
|
|
2575
2824
|
if (includeConversationContext && conversationId !== void 0 && conversationId !== "") {
|
|
2576
2825
|
try {
|
|
@@ -2667,8 +2916,44 @@ var SynapClient = class {
|
|
|
2667
2916
|
if (summary === null) return;
|
|
2668
2917
|
mergeUserSummary(response, summary);
|
|
2669
2918
|
}
|
|
2670
|
-
/**
|
|
2919
|
+
/**
|
|
2920
|
+
* Whether the SDK is authoritative for short-term context.
|
|
2921
|
+
*
|
|
2922
|
+
* Resolution order matches Python's `_is_st_authoritative`: the explicit
|
|
2923
|
+
* option first, then `SYNAP_SDK_ST_AUTHORITATIVE` (truthy strings only).
|
|
2924
|
+
* Defaults to false, so nothing changes for a caller who never sets it.
|
|
2925
|
+
*/
|
|
2926
|
+
#isStAuthoritative() {
|
|
2927
|
+
if (this.#stAuthoritative === true) return true;
|
|
2928
|
+
const raw = (getEnv("SYNAP_SDK_ST_AUTHORITATIVE") ?? "").trim().toLowerCase();
|
|
2929
|
+
return ["1", "true", "yes", "on"].includes(raw);
|
|
2930
|
+
}
|
|
2931
|
+
/**
|
|
2932
|
+
* Render `get_context_for_prompt` from the local store, or null to fall
|
|
2933
|
+
* through to the network. Warm means a compaction landed OR raw turns are
|
|
2934
|
+
* buffered: with neither there is nothing to render and the server's answer
|
|
2935
|
+
* is the only real one.
|
|
2936
|
+
*/
|
|
2937
|
+
#localPrompt(conversationId, style) {
|
|
2938
|
+
if (!this.#isStAuthoritative()) return null;
|
|
2939
|
+
const entry = this.#shortTerm.get(conversationId);
|
|
2940
|
+
if (entry === null) return null;
|
|
2941
|
+
if (entry.compactionId === null && entry.recentTurns.length === 0) return null;
|
|
2942
|
+
return renderForPrompt(entry, style);
|
|
2943
|
+
}
|
|
2944
|
+
/** Same, for `get_compacted`. Requires an actual compaction, not just turns. */
|
|
2945
|
+
#localCompacted(conversationId, format) {
|
|
2946
|
+
if (!this.#isStAuthoritative()) return null;
|
|
2947
|
+
const entry = this.#shortTerm.get(conversationId);
|
|
2948
|
+
if (entry === null || entry.compactionId === null) return null;
|
|
2949
|
+
return renderCompacted(entry, format);
|
|
2950
|
+
}
|
|
2951
|
+
/** Every diagnostic the SDK emits passes through here. */
|
|
2671
2952
|
#warn(message) {
|
|
2953
|
+
if (this.#logger !== void 0) {
|
|
2954
|
+
this.#logger("warn", message);
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2672
2957
|
console.warn(`[synap] ${message}`);
|
|
2673
2958
|
}
|
|
2674
2959
|
/** Release timers and connections. Safe to call more than once. */
|
|
@@ -2689,7 +2974,8 @@ function pick3(o2, snake, camel) {
|
|
|
2689
2974
|
return a !== void 0 ? a : o2[camel];
|
|
2690
2975
|
}
|
|
2691
2976
|
function resolveBaseUrl(options) {
|
|
2692
|
-
|
|
2977
|
+
const explicit = options.baseUrl ?? options.api_base_url;
|
|
2978
|
+
if (explicit !== void 0) return explicit;
|
|
2693
2979
|
const fromEnv = getEnv("SYNAP_BASE_URL")?.trim();
|
|
2694
2980
|
return fromEnv !== void 0 && fromEnv !== "" ? fromEnv : void 0;
|
|
2695
2981
|
}
|
|
@@ -2712,6 +2998,7 @@ export {
|
|
|
2712
2998
|
AnticipationCache,
|
|
2713
2999
|
AuthenticationError,
|
|
2714
3000
|
BM25,
|
|
3001
|
+
COMPACTION_LEVELS,
|
|
2715
3002
|
CONTRACT,
|
|
2716
3003
|
ConflictError,
|
|
2717
3004
|
ConnectionError,
|