@maximem/synap-js-sdk 0.4.8 → 0.5.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.
Files changed (39) hide show
  1. package/dist/anticipation-cache-LRE3TLK4.cjs.map +1 -1
  2. package/dist/{chunk-T5QQBJ6T.js → chunk-ASM2OIIG.js} +292 -7
  3. package/dist/chunk-ASM2OIIG.js.map +1 -0
  4. package/dist/{chunk-OCMM2Z5P.js → chunk-BWMPKLFG.js} +40 -2
  5. package/dist/chunk-BWMPKLFG.js.map +1 -0
  6. package/dist/{chunk-7CWBRHUO.cjs → chunk-IA2N5NUY.cjs} +302 -17
  7. package/dist/chunk-IA2N5NUY.cjs.map +1 -0
  8. package/dist/{chunk-2TVL5IPQ.cjs → chunk-T372OL3Q.cjs} +41 -3
  9. package/dist/chunk-T372OL3Q.cjs.map +1 -0
  10. package/dist/chunk-T6U5PVAD.cjs.map +1 -1
  11. package/dist/chunk-ZKOJDUN4.cjs.map +1 -1
  12. package/dist/env-55ULHNJZ.cjs.map +1 -1
  13. package/dist/errors-Bl3vY1eb.d.cts +382 -0
  14. package/dist/errors-Bl3vY1eb.d.ts +382 -0
  15. package/dist/grpc/index.cjs +7 -7
  16. package/dist/grpc/index.cjs.map +1 -1
  17. package/dist/grpc/index.d.cts +44 -4
  18. package/dist/grpc/index.d.ts +44 -4
  19. package/dist/grpc/index.js +5 -5
  20. package/dist/index.cjs +278 -161
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +97 -148
  23. package/dist/index.d.ts +97 -148
  24. package/dist/index.js +171 -54
  25. package/dist/index.js.map +1 -1
  26. package/dist/stream-client-6OWRYOA4.js +16 -0
  27. package/dist/stream-client-Y5JGSF2R.cjs +16 -0
  28. package/dist/stream-client-Y5JGSF2R.cjs.map +1 -0
  29. package/package.json +1 -1
  30. package/dist/anticipation-cache-C1rUEpaW.d.cts +0 -211
  31. package/dist/anticipation-cache-C1rUEpaW.d.ts +0 -211
  32. package/dist/chunk-2TVL5IPQ.cjs.map +0 -1
  33. package/dist/chunk-7CWBRHUO.cjs.map +0 -1
  34. package/dist/chunk-OCMM2Z5P.js.map +0 -1
  35. package/dist/chunk-T5QQBJ6T.js.map +0 -1
  36. package/dist/stream-client-2C5KIOWP.js +0 -7
  37. package/dist/stream-client-GPU37E6F.cjs +0 -7
  38. package/dist/stream-client-GPU37E6F.cjs.map +0 -1
  39. /package/dist/{stream-client-2C5KIOWP.js.map → stream-client-6OWRYOA4.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,3 +1,17 @@
1
+ import {
2
+ ANY_SCOPE,
3
+ AnticipationCache,
4
+ BM25,
5
+ CONTRACT,
6
+ THRESHOLDS,
7
+ isRecallQuery,
8
+ rungKey,
9
+ stem,
10
+ tokenize
11
+ } from "./chunk-ODVOQOSO.js";
12
+ import {
13
+ getEnv
14
+ } from "./chunk-QBCVMPGK.js";
1
15
  import {
2
16
  AgentUnavailableError,
3
17
  AuthenticationError,
@@ -21,39 +35,9 @@ import {
21
35
  SynapTransientError,
22
36
  TranscriptConflictError,
23
37
  TransientError,
24
- isSynapError
25
- } from "./chunk-OCMM2Z5P.js";
26
- import {
27
- ANY_SCOPE,
28
- AnticipationCache,
29
- BM25,
30
- CONTRACT,
31
- THRESHOLDS,
32
- isRecallQuery,
33
- rungKey,
34
- stem,
35
- tokenize
36
- } from "./chunk-ODVOQOSO.js";
37
- import {
38
- getEnv
39
- } from "./chunk-QBCVMPGK.js";
40
-
41
- // src/util/correlation.ts
42
- function newCorrelationId() {
43
- const c = globalThis.crypto;
44
- if (typeof c?.randomUUID === "function") return c.randomUUID();
45
- if (typeof c?.getRandomValues === "function") {
46
- const b = c.getRandomValues(new Uint8Array(16));
47
- b[6] = (b[6] ?? 0) & 15 | 64;
48
- b[8] = (b[8] ?? 0) & 63 | 128;
49
- const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
50
- return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
51
- }
52
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
53
- const r = Math.random() * 16 | 0;
54
- return (ch === "x" ? r : r & 3 | 8).toString(16);
55
- });
56
- }
38
+ isSynapError,
39
+ newCorrelationId
40
+ } from "./chunk-BWMPKLFG.js";
57
41
 
58
42
  // src/transport/endpoints.ts
59
43
  var ENDPOINTS = {
@@ -423,6 +407,16 @@ function toTransportError(error, correlationId) {
423
407
  { correlationId, cause: error }
424
408
  );
425
409
  }
410
+ var CREDIT_ERRORS = /* @__PURE__ */ new Set(["insufficient_credits", "trial_limit_reached"]);
411
+ function creditPayload(parsed, detailObject) {
412
+ return detailObject ?? parsed;
413
+ }
414
+ function creditReason(payload) {
415
+ const reason = stringOrNull(payload["reason"]);
416
+ if (reason !== null) return reason;
417
+ const error = stringOrNull(payload["error"]);
418
+ return error !== null && CREDIT_ERRORS.has(error) ? error : null;
419
+ }
426
420
  async function toHttpError(response, correlationId) {
427
421
  const body = await response.text().catch(() => "");
428
422
  let parsed = {};
@@ -444,14 +438,18 @@ async function toHttpError(response, correlationId) {
444
438
  return new AuthenticationError(message, opts);
445
439
  case 404:
446
440
  return new ContextNotFoundError(message, opts);
447
- case 402:
441
+ case 402: {
442
+ const credit = creditPayload(parsed, detailObject);
448
443
  return new InsufficientCreditsError(message, {
449
444
  ...opts,
450
- balanceCredits: numberOrNull(parsed["balance_credits"]),
451
- requiredCredits: numberOrNull(parsed["minimum_required_credits"]) ?? numberOrNull(parsed["required_credits"]),
452
- recoveryUrl: stringOrNull(parsed["recovery_url"]),
453
- redeemUrl: stringOrNull(parsed["redeem_url"])
445
+ balanceCredits: numberOrNull(credit["balance_credits"]),
446
+ requiredCredits: numberOrNull(credit["minimum_required_credits"]) ?? numberOrNull(credit["required_credits"]),
447
+ recoveryUrl: stringOrNull(credit["recovery_url"]),
448
+ redeemUrl: stringOrNull(credit["redeem_url"]),
449
+ reason: creditReason(credit),
450
+ manageUrl: stringOrNull(credit["manage_url"])
454
451
  });
452
+ }
455
453
  case 409:
456
454
  if (detailObject !== null && detailObject["code"] === "transcript_conflict") {
457
455
  return new TranscriptConflictError(message, opts);
@@ -460,9 +458,13 @@ async function toHttpError(response, correlationId) {
460
458
  case 429: {
461
459
  const header = response.headers.get("retry-after");
462
460
  const retryAfter = header !== null && header.trim() !== "" ? Number(header) : null;
461
+ const credit = creditPayload(parsed, detailObject);
463
462
  return new RateLimitError(message, {
464
463
  ...opts,
465
- retryAfterSeconds: retryAfter !== null && Number.isFinite(retryAfter) ? retryAfter : null
464
+ retryAfterSeconds: retryAfter !== null && Number.isFinite(retryAfter) ? retryAfter : null,
465
+ reason: creditReason(credit),
466
+ upgradeUrl: stringOrNull(credit["upgrade_url"]),
467
+ manageUrl: stringOrNull(credit["manage_url"])
466
468
  });
467
469
  }
468
470
  case 503:
@@ -1047,6 +1049,8 @@ function buildRecordMessageBody(options) {
1047
1049
  };
1048
1050
  const sessionId = either2(o2, "session_id", "sessionId");
1049
1051
  if (sessionId !== void 0) body["session_id"] = sessionId;
1052
+ const scopePath = either2(o2, "scope_path", "scopePath");
1053
+ if (scopePath !== void 0) body["scope"] = scopePath;
1050
1054
  return body;
1051
1055
  }
1052
1056
  function buildIngestTranscriptBody(options) {
@@ -1073,6 +1077,8 @@ function buildIngestTranscriptBody(options) {
1073
1077
  if (options.metadata !== void 0) body["metadata"] = options.metadata;
1074
1078
  if (startedAt !== void 0) body["started_at"] = startedAt;
1075
1079
  if (endedAt !== void 0) body["ended_at"] = endedAt;
1080
+ const scopePath = either2(o2, "scope_path", "scopePath");
1081
+ if (scopePath !== void 0) body["scope"] = scopePath;
1076
1082
  return body;
1077
1083
  }
1078
1084
  function buildCompactBody(options) {
@@ -1372,8 +1378,24 @@ function thinkingMetadata(options) {
1372
1378
  }
1373
1379
  return md;
1374
1380
  }
1381
+ var KNOWN_EVENT_TYPES = /* @__PURE__ */ new Set([
1382
+ "user_message",
1383
+ "assistant_message",
1384
+ "tool_call",
1385
+ "tool_result",
1386
+ "agent_thinking",
1387
+ "context_request",
1388
+ "context_fetch",
1389
+ "session_start",
1390
+ "session_end"
1391
+ ]);
1392
+ function toolResultJson(result) {
1393
+ if (result === void 0 || result === null) return "";
1394
+ return typeof result === "string" ? result : JSON.stringify(result);
1395
+ }
1375
1396
  function createInstanceNamespace(credentials, cache, onTurn) {
1376
1397
  let client = null;
1398
+ const openSessions = /* @__PURE__ */ new Map();
1377
1399
  let dispatchCompaction = null;
1378
1400
  let applyCompaction = null;
1379
1401
  const internals = {
@@ -1397,16 +1419,33 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1397
1419
  return client !== null && client.isConnected;
1398
1420
  }
1399
1421
  };
1422
+ function ensureSession(conversationId, userId, customerId) {
1423
+ if (!conversationId || !userId || client === null) return;
1424
+ if (openSessions.has(conversationId)) return;
1425
+ const sessionId = newCorrelationId();
1426
+ try {
1427
+ client.sendSessionControl({
1428
+ action: "start",
1429
+ session_id: sessionId,
1430
+ conversation_id: conversationId,
1431
+ ...userId ? { user_id: userId } : {},
1432
+ ...customerId ? { customer_id: customerId } : {}
1433
+ });
1434
+ openSessions.set(conversationId, sessionId);
1435
+ } catch {
1436
+ }
1437
+ }
1400
1438
  const namespace = {
1401
1439
  async listen(options = {}) {
1402
1440
  if (client !== null) {
1403
1441
  throw new ListeningAlreadyActiveError("Listening is already active on this instance");
1404
1442
  }
1405
- const { GrpcStreamClient } = await import("./stream-client-2C5KIOWP.js");
1443
+ const { GrpcStreamClient } = await import("./stream-client-6OWRYOA4.js");
1406
1444
  const onContext = options.on_context ?? options.onContext;
1407
1445
  const onDisconnect = options.on_disconnect ?? options.onDisconnect;
1408
1446
  const onReconnect = options.on_reconnect ?? options.onReconnect;
1409
1447
  let reconnectAttempts = 0;
1448
+ let stream = null;
1410
1449
  const created = new GrpcStreamClient(credentials(), cache, {
1411
1450
  host: options.host ?? getEnv("SYNAP_GRPC_HOST") ?? DEFAULT_GRPC_HOST,
1412
1451
  port: options.port ?? envPort() ?? DEFAULT_GRPC_PORT,
@@ -1416,14 +1455,18 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1416
1455
  internals.onCompactionUpdate(conversationId, bundle);
1417
1456
  },
1418
1457
  onStateChange: (state) => {
1458
+ if (state !== "connected") openSessions.clear();
1419
1459
  if (state === "reconnecting") {
1420
1460
  reconnectAttempts += 1;
1421
1461
  onReconnect?.(reconnectAttempts);
1422
1462
  } else if (state === "disconnected") {
1423
- onDisconnect?.("stream disconnected");
1463
+ const stopped = stream?.lastError ?? null;
1464
+ const reason = stopped === null ? "stream disconnected" : `credit_stop:${stopped.reason ?? "unknown"}`;
1465
+ onDisconnect?.(reason);
1424
1466
  }
1425
1467
  }
1426
1468
  });
1469
+ stream = created;
1427
1470
  try {
1428
1471
  await created.connect();
1429
1472
  } catch (error) {
@@ -1437,29 +1480,58 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1437
1480
  },
1438
1481
  async stop_listening() {
1439
1482
  if (client === null) return;
1483
+ for (const conversationId of [...openSessions.keys()]) {
1484
+ await namespace.end_session(conversationId);
1485
+ }
1440
1486
  const active = client;
1441
1487
  client = null;
1442
1488
  await active.disconnect();
1443
1489
  },
1490
+ async end_session(conversationId) {
1491
+ const sessionId = openSessions.get(conversationId);
1492
+ if (sessionId === void 0 || client === null) return;
1493
+ openSessions.delete(conversationId);
1494
+ try {
1495
+ client.sendSessionControl({
1496
+ action: "end",
1497
+ session_id: sessionId,
1498
+ conversation_id: conversationId
1499
+ });
1500
+ } catch {
1501
+ }
1502
+ },
1444
1503
  async send_message(options) {
1445
1504
  if (client === null) throw new ListeningNotActiveError("Listening is not active");
1446
1505
  if (options.content === void 0) {
1447
1506
  throw new ListeningNotActiveError("content is required");
1448
1507
  }
1508
+ const eventType = options.event_type ?? options.eventType ?? "user_message";
1509
+ if (!KNOWN_EVENT_TYPES.has(eventType)) {
1510
+ throw new SynapError(
1511
+ `unknown event_type '${eventType}'. Expected one of: ` + [...KNOWN_EVENT_TYPES].sort().join(", ")
1512
+ );
1513
+ }
1449
1514
  const conversationId = options.conversation_id ?? options.conversationId ?? "";
1450
- onTurn?.({
1515
+ ensureSession(
1451
1516
  conversationId,
1452
- role: options.role ?? "user",
1453
- content: options.content
1454
- });
1517
+ options.user_id ?? options.userId ?? "",
1518
+ options.customer_id ?? options.customerId ?? ""
1519
+ );
1520
+ if (eventType === "user_message" || eventType === "assistant_message") {
1521
+ onTurn?.({
1522
+ conversationId,
1523
+ role: options.role ?? "user",
1524
+ content: options.content
1525
+ });
1526
+ }
1455
1527
  client.sendConversationEvent({
1456
- event_type: options.event_type ?? options.eventType ?? "user_message",
1528
+ event_type: eventType,
1457
1529
  content: options.content,
1458
1530
  role: options.role ?? "user",
1459
1531
  conversation_id: options.conversation_id ?? options.conversationId ?? "",
1460
1532
  user_id: options.user_id ?? options.userId ?? "",
1461
1533
  customer_id: options.customer_id ?? options.customerId ?? "",
1462
- session_id: options.session_id ?? options.sessionId ?? "",
1534
+ session_id: options.session_id ?? options.sessionId ?? openSessions.get(conversationId) ?? "",
1463
1535
  metadata: options.metadata ?? {},
1464
1536
  timestamp_ms: Date.now(),
1465
1537
  tool_name: options.tool_name ?? options.toolName ?? "",
@@ -1468,17 +1540,61 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1468
1540
  const a = options.tool_args ?? options.toolArgs;
1469
1541
  return a === void 0 ? "" : JSON.stringify(a);
1470
1542
  })(),
1543
+ tool_result_json: toolResultJson(options.tool_result ?? options.toolResult),
1544
+ tool_call_id: options.tool_call_id ?? options.toolCallId ?? "",
1471
1545
  search_queries: options.search_queries ?? options.searchQueries ?? [],
1472
1546
  context_types: options.context_types ?? options.contextTypes ?? []
1473
1547
  });
1474
1548
  },
1549
+ async record_tool_call(options) {
1550
+ await namespace.send_message({
1551
+ content: "",
1552
+ role: "assistant",
1553
+ event_type: "tool_call",
1554
+ tool_name: options.tool_name ?? options.toolName ?? "",
1555
+ ...options.tool_args ?? options.toolArgs ? { tool_args: options.tool_args ?? options.toolArgs } : {},
1556
+ tool_call_id: options.tool_call_id ?? options.toolCallId ?? "",
1557
+ conversation_id: options.conversation_id ?? options.conversationId ?? "",
1558
+ user_id: options.user_id ?? options.userId ?? "",
1559
+ customer_id: options.customer_id ?? options.customerId ?? "",
1560
+ session_id: options.session_id ?? options.sessionId ?? "",
1561
+ metadata: options.metadata ?? {}
1562
+ });
1563
+ },
1564
+ async record_tool_result(options) {
1565
+ await namespace.send_message({
1566
+ content: "",
1567
+ role: "tool",
1568
+ event_type: "tool_result",
1569
+ tool_result: options.result,
1570
+ tool_name: options.tool_name ?? options.toolName ?? "",
1571
+ tool_call_id: options.tool_call_id ?? options.toolCallId ?? "",
1572
+ conversation_id: options.conversation_id ?? options.conversationId ?? "",
1573
+ user_id: options.user_id ?? options.userId ?? "",
1574
+ customer_id: options.customer_id ?? options.customerId ?? "",
1575
+ session_id: options.session_id ?? options.sessionId ?? "",
1576
+ metadata: options.metadata ?? {}
1577
+ });
1578
+ },
1475
1579
  async record_thinking(options) {
1476
1580
  if (client === null) throw new ListeningNotActiveError("Listening is not active");
1581
+ const conversationId = options.conversation_id ?? options.conversationId ?? "";
1582
+ ensureSession(
1583
+ conversationId,
1584
+ options.user_id ?? options.userId ?? "",
1585
+ options.customer_id ?? options.customerId ?? ""
1586
+ );
1477
1587
  client.sendConversationEvent({
1478
1588
  // Python emits reasoning as an `agent_thinking` conversation event.
1479
1589
  event_type: "agent_thinking",
1480
1590
  content: options.content,
1481
- role: "assistant",
1591
+ // Deliberately no role. A thought is not the assistant's reply, and
1592
+ // this went out as 'assistant' from the day it shipped: the server
1593
+ // checked role before event_type, so every reasoning step any client
1594
+ // sent was filed as the final answer and the reasoning path never ran.
1595
+ // Sending no role is correct against both the fixed server and one that
1596
+ // has not been updated yet.
1597
+ role: "",
1482
1598
  conversation_id: options.conversation_id ?? options.conversationId ?? "",
1483
1599
  user_id: options.user_id ?? options.userId ?? "",
1484
1600
  customer_id: options.customer_id ?? options.customerId ?? "",
@@ -1496,7 +1612,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1496
1612
  }
1497
1613
 
1498
1614
  // src/version.ts
1499
- var SDK_VERSION = "0.4.7";
1615
+ var SDK_VERSION = "0.5.1";
1500
1616
 
1501
1617
  // src/registry.ts
1502
1618
  var instances = /* @__PURE__ */ new Map();
@@ -1685,7 +1801,7 @@ function budgetRecentTurns(turns) {
1685
1801
  return windowed;
1686
1802
  }
1687
1803
  function overlayLocalRecentTurns(response, store, conversationId, configured) {
1688
- if (conversationId === void 0 || conversationId === "") return;
1804
+ if (conversationId === void 0 || conversationId === null || conversationId === "") return;
1689
1805
  try {
1690
1806
  const entry = store.get(conversationId);
1691
1807
  if (entry === null) return;
@@ -2781,7 +2897,8 @@ var SynapClient = class {
2781
2897
  };
2782
2898
  const labels = [];
2783
2899
  const tasks = [];
2784
- if (conversationId !== void 0 && conversationId !== "" && wants("conversation")) {
2900
+ const present = (v) => v !== void 0 && v !== null && v !== "";
2901
+ if (present(conversationId) && wants("conversation")) {
2785
2902
  labels.push("conversation");
2786
2903
  tasks.push(this.conversation.context.fetch({
2787
2904
  conversation_id: conversationId,
@@ -2790,7 +2907,7 @@ var SynapClient = class {
2790
2907
  customer_id: customerId
2791
2908
  }));
2792
2909
  }
2793
- if (userId !== void 0 && userId !== "" && wants("user")) {
2910
+ if (present(userId) && wants("user")) {
2794
2911
  labels.push("user");
2795
2912
  tasks.push(this.user.context.fetch({
2796
2913
  user_id: userId,
@@ -2802,7 +2919,7 @@ var SynapClient = class {
2802
2919
  last_n_conversations: lastNConversations
2803
2920
  }));
2804
2921
  }
2805
- if (customerId !== void 0 && customerId !== "" && wants("customer")) {
2922
+ if (present(customerId) && wants("customer")) {
2806
2923
  labels.push("customer");
2807
2924
  tasks.push(this.customer.context.fetch({
2808
2925
  customer_id: customerId,
@@ -2836,7 +2953,7 @@ var SynapClient = class {
2836
2953
  for (const [, response] of successful) {
2837
2954
  overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
2838
2955
  }
2839
- if (includeConversationContext && conversationId !== void 0 && conversationId !== "") {
2956
+ if (includeConversationContext && present(conversationId)) {
2840
2957
  try {
2841
2958
  merged.conversation_context = await this.conversation.context.get_context_for_prompt({
2842
2959
  conversation_id: conversationId