@maximem/synap-js-sdk 0.4.7 → 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 (50) hide show
  1. package/dist/anticipation-cache-LRE3TLK4.cjs +14 -0
  2. package/dist/anticipation-cache-LRE3TLK4.cjs.map +1 -0
  3. package/dist/anticipation-cache-WLU4NMD5.js +14 -0
  4. package/dist/{chunk-ZU6ZC4SU.js → chunk-ASM2OIIG.js} +305 -7
  5. package/dist/chunk-ASM2OIIG.js.map +1 -0
  6. package/dist/{chunk-OCMM2Z5P.js → chunk-BWMPKLFG.js} +40 -2
  7. package/dist/chunk-BWMPKLFG.js.map +1 -0
  8. package/dist/{chunk-3JSE64GW.cjs → chunk-IA2N5NUY.cjs} +315 -17
  9. package/dist/chunk-IA2N5NUY.cjs.map +1 -0
  10. package/dist/{chunk-6LDUDKHS.js → chunk-ODVOQOSO.js} +124 -8
  11. package/dist/chunk-ODVOQOSO.js.map +1 -0
  12. package/dist/{chunk-2TVL5IPQ.cjs → chunk-T372OL3Q.cjs} +41 -3
  13. package/dist/chunk-T372OL3Q.cjs.map +1 -0
  14. package/dist/{chunk-RB57I2IQ.cjs → chunk-T6U5PVAD.cjs} +125 -9
  15. package/dist/chunk-T6U5PVAD.cjs.map +1 -0
  16. package/dist/chunk-ZKOJDUN4.cjs.map +1 -1
  17. package/dist/env-55ULHNJZ.cjs.map +1 -1
  18. package/dist/errors-Bl3vY1eb.d.cts +382 -0
  19. package/dist/errors-Bl3vY1eb.d.ts +382 -0
  20. package/dist/grpc/index.cjs +8 -8
  21. package/dist/grpc/index.cjs.map +1 -1
  22. package/dist/grpc/index.d.cts +44 -4
  23. package/dist/grpc/index.d.ts +44 -4
  24. package/dist/grpc/index.js +6 -6
  25. package/dist/index.cjs +301 -173
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +102 -146
  28. package/dist/index.d.ts +102 -146
  29. package/dist/index.js +192 -64
  30. package/dist/index.js.map +1 -1
  31. package/dist/stream-client-6OWRYOA4.js +16 -0
  32. package/dist/stream-client-Y5JGSF2R.cjs +16 -0
  33. package/dist/stream-client-Y5JGSF2R.cjs.map +1 -0
  34. package/package.json +6 -1
  35. package/dist/anticipation-cache-ARYEEKMU.cjs +0 -10
  36. package/dist/anticipation-cache-ARYEEKMU.cjs.map +0 -1
  37. package/dist/anticipation-cache-BMVF5sXb.d.cts +0 -190
  38. package/dist/anticipation-cache-BMVF5sXb.d.ts +0 -190
  39. package/dist/anticipation-cache-H7HEVWQC.js +0 -10
  40. package/dist/chunk-2TVL5IPQ.cjs.map +0 -1
  41. package/dist/chunk-3JSE64GW.cjs.map +0 -1
  42. package/dist/chunk-6LDUDKHS.js.map +0 -1
  43. package/dist/chunk-OCMM2Z5P.js.map +0 -1
  44. package/dist/chunk-RB57I2IQ.cjs.map +0 -1
  45. package/dist/chunk-ZU6ZC4SU.js.map +0 -1
  46. package/dist/stream-client-6V4FGKZ3.js +0 -7
  47. package/dist/stream-client-PRTOH62B.cjs +0 -7
  48. package/dist/stream-client-PRTOH62B.cjs.map +0 -1
  49. /package/dist/{anticipation-cache-H7HEVWQC.js.map → anticipation-cache-WLU4NMD5.js.map} +0 -0
  50. /package/dist/{stream-client-6V4FGKZ3.js.map → stream-client-6OWRYOA4.js.map} +0 -0
@@ -56,9 +56,18 @@ var NetworkTimeoutError = class extends SynapTransientError {
56
56
  var RateLimitError = class extends SynapTransientError {
57
57
  static errorCode = "rate_limit";
58
58
  retryAfterSeconds;
59
+ /** Machine-readable cause, when the stop was a credit stop. */
60
+ reason;
61
+ /** Where a Trial customer upgrades. Python calls this `upgrade_url`. */
62
+ upgradeUrl;
63
+ /** Where billing is managed. Python calls this `manage_url`. */
64
+ manageUrl;
59
65
  constructor(message, options = {}) {
60
66
  super(message, options);
61
67
  this.retryAfterSeconds = options.retryAfterSeconds ?? null;
68
+ this.reason = options.reason ?? null;
69
+ this.upgradeUrl = options.upgradeUrl ?? null;
70
+ this.manageUrl = options.manageUrl ?? null;
62
71
  }
63
72
  };
64
73
  var ServiceUnavailableError = class extends SynapTransientError {
@@ -78,12 +87,23 @@ var InsufficientCreditsError = class extends SynapPermanentError {
78
87
  recoveryUrl;
79
88
  /** Where a redeem code can be entered. Python calls this `redeem_url`. */
80
89
  redeemUrl;
90
+ /**
91
+ * Machine-readable cause. `'overages_disabled'` means the plan is paid and
92
+ * the balance is spent, but the account has not allowed usage past zero, so
93
+ * the work stops until someone allows overages or adds credits. Null when an
94
+ * older server did not send one.
95
+ */
96
+ reason;
97
+ /** Where that setting and billing are managed. Python calls this `manage_url`. */
98
+ manageUrl;
81
99
  constructor(message, options = {}) {
82
100
  super(message, options);
83
101
  this.balanceCredits = options.balanceCredits ?? null;
84
102
  this.requiredCredits = options.requiredCredits ?? null;
85
103
  this.recoveryUrl = options.recoveryUrl ?? null;
86
104
  this.redeemUrl = options.redeemUrl ?? null;
105
+ this.reason = options.reason ?? null;
106
+ this.manageUrl = options.manageUrl ?? null;
87
107
  }
88
108
  };
89
109
  var InvalidInputError = class extends SynapPermanentError {
@@ -150,6 +170,23 @@ var TransientError = SynapTransientError;
150
170
  var PermanentError = SynapPermanentError;
151
171
  var ConnectionError = NetworkTimeoutError;
152
172
 
173
+ // src/util/correlation.ts
174
+ function newCorrelationId() {
175
+ const c = globalThis.crypto;
176
+ if (typeof c?.randomUUID === "function") return c.randomUUID();
177
+ if (typeof c?.getRandomValues === "function") {
178
+ const b = c.getRandomValues(new Uint8Array(16));
179
+ b[6] = (b[6] ?? 0) & 15 | 64;
180
+ b[8] = (b[8] ?? 0) & 63 | 128;
181
+ const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
182
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
183
+ }
184
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
185
+ const r = Math.random() * 16 | 0;
186
+ return (ch === "x" ? r : r & 3 | 8).toString(16);
187
+ });
188
+ }
189
+
153
190
  export {
154
191
  SynapError,
155
192
  isSynapError,
@@ -173,6 +210,7 @@ export {
173
210
  SDKError,
174
211
  TransientError,
175
212
  PermanentError,
176
- ConnectionError
213
+ ConnectionError,
214
+ newCorrelationId
177
215
  };
178
- //# sourceMappingURL=chunk-OCMM2Z5P.js.map
216
+ //# sourceMappingURL=chunk-BWMPKLFG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/util/correlation.ts"],"sourcesContent":["/**\n * Synap SDK error taxonomy.\n *\n * Mirrors `maximem_synap/models/errors.py` 1:1, including the four\n * backward-compatibility aliases at the bottom. If you add an error to one\n * SDK, add it to the other in the same change.\n *\n * ## Why every error carries a `.code`\n *\n * This package ships dual ESM + CJS. A consumer whose dependency graph pulls\n * in both formats gets *two* copies of every class in this file, and then:\n *\n * try { ... } catch (e) { if (e instanceof SynapError) { ... } }\n *\n * silently evaluates to `false` against the copy from the other format. Error\n * handling stops working and nothing throws to tell you. `.code` is a plain\n * string, so it is immune. Prefer it at package boundaries:\n *\n * if (isSynapError(e) && e.code === 'rate_limit') { ... }\n *\n * `Symbol.hasInstance` is also wired up on the base class, so `instanceof`\n * works across realms too -- but `.code` is the documented contract.\n */\n\n/** Stable, format-independent discriminators. Never renumber or reuse. */\nexport type SynapErrorCode =\n | 'synap_error'\n | 'transient'\n | 'permanent'\n | 'network_timeout'\n | 'rate_limit'\n | 'insufficient_credits'\n | 'service_unavailable'\n | 'invalid_input'\n | 'invalid_instance_id'\n | 'invalid_conversation_id'\n | 'authentication'\n | 'context_not_found'\n | 'conflict'\n | 'transcript_conflict'\n | 'session_expired'\n | 'listening_already_active'\n | 'listening_not_active'\n | 'agent_unavailable';\n\nconst BRAND = Symbol.for('@maximem/synap-js-sdk.SynapError');\n\nexport interface SynapErrorOptions {\n correlationId?: string | null;\n cause?: unknown;\n}\n\nexport class SynapError extends Error {\n /**\n * The single declaration site for each subclass's code.\n *\n * It is `static` rather than a class field on purpose. Under\n * `useDefineForClassFields` (the default at our ES2022 target) a class field\n * is an *instance* property, so it is invisible from the constructor object.\n * `Symbol.hasInstance` below only has the constructor to work with, so a\n * field would leave it unable to tell `AuthenticationError` from\n * `RateLimitError` -- it would report every Synap error as an instance of\n * every other one.\n */\n static readonly errorCode: SynapErrorCode = 'synap_error';\n /** True for errors where a retry may succeed. Static for the same reason. */\n static readonly isTransient: boolean = false;\n\n /** Own, enumerable, so logs and JSON.stringify include it. */\n readonly code: SynapErrorCode;\n readonly correlationId: string | null;\n readonly transient: boolean;\n\n constructor(message: string, options: SynapErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n const ctor = new.target as typeof SynapError;\n this.name = new.target.name;\n this.code = ctor.errorCode;\n this.transient = ctor.isTransient;\n this.correlationId = options.correlationId ?? null;\n Object.defineProperty(this, BRAND, { value: true, enumerable: false });\n // Without this, subclassing a built-in under a downlevel target breaks the\n // prototype chain and `instanceof` fails even within a single copy.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static override [Symbol.hasInstance](instance: unknown): boolean {\n if (instance === null || typeof instance !== 'object') return false;\n // Cross-realm / dual-format tolerant: identify by brand, then narrow by the\n // subclass's own code so `e instanceof RateLimitError` stays meaningful.\n if (!(BRAND in instance)) return false;\n const expected = (this as unknown as { errorCode?: SynapErrorCode }).errorCode;\n // Called on something that is not one of our classes: fall back to the brand.\n if (expected === undefined) return true;\n // Everything branded descends from the root.\n if (expected === 'synap_error') return true;\n const actual = (instance as { code?: string }).code;\n return actual === expected || descendsFrom(actual, expected);\n }\n}\n\n/** Type guard that works across ESM/CJS copies and realms. */\nexport function isSynapError(e: unknown): e is SynapError {\n return typeof e === 'object' && e !== null && BRAND in e;\n}\n\nexport class SynapTransientError extends SynapError {\n static override readonly errorCode: SynapErrorCode = 'transient';\n static override readonly isTransient: boolean = true;\n}\n\nexport class SynapPermanentError extends SynapError {\n static override readonly errorCode: SynapErrorCode = 'permanent';\n static override readonly isTransient: boolean = false;\n}\n\n// ─── Transient ────────────────────────────────────────────────────────────────\n\nexport class NetworkTimeoutError extends SynapTransientError {\n static override readonly errorCode: SynapErrorCode = 'network_timeout';\n}\n\n/**\n * Rate limit exceeded (HTTP 429, gRPC RESOURCE_EXHAUSTED).\n *\n * Also carries the two credit stops the server reports as 429, because both\n * recover with time or a plan change rather than with a retry of this request:\n * `reason: 'trial_limit_reached'` (the Trial cap is spent, see `upgradeUrl`)\n * and `reason: 'subscription_inactive'` (a paid plan whose subscription is past\n * due, unpaid or cancelled, see `manageUrl`). `reason` is null for an ordinary\n * rate limit and against a server too old to send one.\n */\nexport class RateLimitError extends SynapTransientError {\n static override readonly errorCode: SynapErrorCode = 'rate_limit';\n readonly retryAfterSeconds: number | null;\n /** Machine-readable cause, when the stop was a credit stop. */\n readonly reason: string | null;\n /** Where a Trial customer upgrades. Python calls this `upgrade_url`. */\n readonly upgradeUrl: string | null;\n /** Where billing is managed. Python calls this `manage_url`. */\n readonly manageUrl: string | null;\n\n constructor(\n message: string,\n options: SynapErrorOptions & {\n retryAfterSeconds?: number | null;\n reason?: string | null;\n upgradeUrl?: string | null;\n manageUrl?: string | null;\n } = {},\n ) {\n super(message, options);\n this.retryAfterSeconds = options.retryAfterSeconds ?? null;\n this.reason = options.reason ?? null;\n this.upgradeUrl = options.upgradeUrl ?? null;\n this.manageUrl = options.manageUrl ?? null;\n }\n}\n\nexport class ServiceUnavailableError extends SynapTransientError {\n static override readonly errorCode: SynapErrorCode = 'service_unavailable';\n}\n\nexport class AgentUnavailableError extends SynapTransientError {\n static override readonly errorCode: SynapErrorCode = 'agent_unavailable';\n constructor(message = 'Agent unavailable', options: SynapErrorOptions = {}) {\n super(message, options);\n }\n}\n\n// ─── Permanent ────────────────────────────────────────────────────────────────\n\n/**\n * The caller's credit wallet cannot cover this request.\n *\n * HTTP 402, and gRPC RESOURCE_EXHAUSTED with credit-related trailing metadata.\n * Permanent by design: retrying without topping up burns quota and never\n * succeeds. This is the error behind \"the agent forgot\" reports where retrieval\n * silently returns nothing.\n */\nexport class InsufficientCreditsError extends SynapPermanentError {\n static override readonly errorCode: SynapErrorCode = 'insufficient_credits';\n readonly balanceCredits: number | null;\n readonly requiredCredits: number | null;\n /** Where the balance can be viewed. Python calls this `recovery_url`. */\n readonly recoveryUrl: string | null;\n /** Where a redeem code can be entered. Python calls this `redeem_url`. */\n readonly redeemUrl: string | null;\n /**\n * Machine-readable cause. `'overages_disabled'` means the plan is paid and\n * the balance is spent, but the account has not allowed usage past zero, so\n * the work stops until someone allows overages or adds credits. Null when an\n * older server did not send one.\n */\n readonly reason: string | null;\n /** Where that setting and billing are managed. Python calls this `manage_url`. */\n readonly manageUrl: string | null;\n\n constructor(\n message: string,\n options: SynapErrorOptions & {\n balanceCredits?: number | null;\n requiredCredits?: number | null;\n recoveryUrl?: string | null;\n redeemUrl?: string | null;\n reason?: string | null;\n manageUrl?: string | null;\n } = {},\n ) {\n super(message, options);\n this.balanceCredits = options.balanceCredits ?? null;\n this.requiredCredits = options.requiredCredits ?? null;\n this.recoveryUrl = options.recoveryUrl ?? null;\n this.redeemUrl = options.redeemUrl ?? null;\n this.reason = options.reason ?? null;\n this.manageUrl = options.manageUrl ?? null;\n }\n}\n\nexport class InvalidInputError extends SynapPermanentError {\n static override readonly errorCode: SynapErrorCode = 'invalid_input';\n}\n\n/**\n * Raised by `validateInstanceId` before any network call, matching Python's\n * `utils/validators.py`. The message text is identical in both SDKs.\n */\nexport class InvalidInstanceIdError extends InvalidInputError {\n static override readonly errorCode: SynapErrorCode = 'invalid_instance_id';\n}\n\n/** See the note on InvalidInstanceIdError. Raised by `validateConversationId`. */\nexport class InvalidConversationIdError extends InvalidInputError {\n static override readonly errorCode: SynapErrorCode = 'invalid_conversation_id';\n}\n\nexport class AuthenticationError extends SynapPermanentError {\n static override readonly errorCode: SynapErrorCode = 'authentication';\n}\n\nexport class ContextNotFoundError extends SynapPermanentError {\n static override readonly errorCode: SynapErrorCode = 'context_not_found';\n}\n\nexport class ConflictError extends SynapPermanentError {\n static override readonly errorCode: SynapErrorCode = 'conflict';\n}\n\nexport class TranscriptConflictError extends ConflictError {\n static override readonly errorCode: SynapErrorCode = 'transcript_conflict';\n}\n\nexport class SessionExpiredError extends SynapPermanentError {\n static override readonly errorCode: SynapErrorCode = 'session_expired';\n}\n\nexport class ListeningAlreadyActiveError extends SynapPermanentError {\n static override readonly errorCode: SynapErrorCode = 'listening_already_active';\n}\n\nexport class ListeningNotActiveError extends SynapPermanentError {\n static override readonly errorCode: SynapErrorCode = 'listening_not_active';\n}\n\n// ─── Hierarchy metadata (drives the cross-copy instanceof above) ──────────────\n\nconst PARENT: Partial<Record<SynapErrorCode, SynapErrorCode>> = {\n transient: 'synap_error',\n permanent: 'synap_error',\n network_timeout: 'transient',\n rate_limit: 'transient',\n service_unavailable: 'transient',\n agent_unavailable: 'transient',\n insufficient_credits: 'permanent',\n invalid_input: 'permanent',\n invalid_instance_id: 'invalid_input',\n invalid_conversation_id: 'invalid_input',\n authentication: 'permanent',\n context_not_found: 'permanent',\n conflict: 'permanent',\n transcript_conflict: 'conflict',\n session_expired: 'permanent',\n listening_already_active: 'permanent',\n listening_not_active: 'permanent',\n};\n\nfunction descendsFrom(actual: string | undefined, expected: string): boolean {\n let cur = actual as SynapErrorCode | undefined;\n const seen = new Set<string>();\n while (cur && !seen.has(cur)) {\n seen.add(cur);\n if (cur === expected) return true;\n cur = PARENT[cur];\n }\n return false;\n}\n\n// ─── Backward-compatibility aliases (mirrors the tail of errors.py) ──────────\n// `ConnectionError` deliberately shadows the global in a module scope. Python\n// does the same thing; keeping the name means ported code reads identically.\n\nexport const SDKError = SynapError;\nexport const TransientError = SynapTransientError;\nexport const PermanentError = SynapPermanentError;\nexport const ConnectionError = NetworkTimeoutError;\n","/**\n * Correlation ID generation.\n *\n * `globalThis.crypto` is unflagged from Node 19 onward and this package\n * declares `engines.node >= 20`, so `randomUUID` is normally present. It is\n * still feature-detected rather than assumed, because `engines` is advisory:\n * npm warns and installs anyway, and the SDK also runs on Workers, Edge and\n * Bun, where the available crypto surface varies.\n *\n * Deliberately does NOT import `node:crypto`. That import is what breaks Edge\n * and Workers builds at bundle time, and a correlation ID is not a security\n * value, so there is nothing to gain from it.\n */\nexport function newCorrelationId(): string {\n const c = globalThis.crypto;\n if (typeof c?.randomUUID === 'function') return c.randomUUID();\n\n if (typeof c?.getRandomValues === 'function') {\n const b = c.getRandomValues(new Uint8Array(16));\n // RFC 4122 version (4) and variant bits. The `?? 0` guards satisfy\n // `noUncheckedIndexedAccess`; a 16-byte array always has these slots.\n b[6] = ((b[6] ?? 0) & 0x0f) | 0x40;\n b[8] = ((b[8] ?? 0) & 0x3f) | 0x80;\n const h = Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('');\n return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n }\n\n // Last resort. Correlation IDs are for tracing only, never for security, so\n // Math.random is acceptable here and only here.\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (ch) => {\n const r = (Math.random() * 16) | 0;\n return (ch === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,11 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/grpc/descriptor.ts
2
- var PROTO_SHA = "1a9a098645d1b3cf";
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+ var _chunkT372OL3Qcjs = require('./chunk-T372OL3Q.cjs');
6
+
7
+ // src/grpc/descriptor.ts
8
+ var PROTO_SHA = "c1d834043608ad4f";
3
9
  var SYNAP_PROTO_DESCRIPTOR = {
4
10
  "nested": {
5
11
  "synap": {
@@ -202,6 +208,14 @@ var SYNAP_PROTO_DESCRIPTOR = {
202
208
  "type": "string",
203
209
  "id": 11
204
210
  },
211
+ "tool_result_json": {
212
+ "type": "string",
213
+ "id": 15
214
+ },
215
+ "tool_call_id": {
216
+ "type": "string",
217
+ "id": 16
218
+ },
205
219
  "search_queries": {
206
220
  "rule": "repeated",
207
221
  "type": "string",
@@ -211,6 +225,19 @@ var SYNAP_PROTO_DESCRIPTOR = {
211
225
  "rule": "repeated",
212
226
  "type": "string",
213
227
  "id": 13
228
+ },
229
+ "scope": {
230
+ "keyType": "string",
231
+ "type": "string",
232
+ "id": 14
233
+ },
234
+ "event_id": {
235
+ "type": "string",
236
+ "id": 17
237
+ },
238
+ "sent_at_ms": {
239
+ "type": "int64",
240
+ "id": 18
214
241
  }
215
242
  }
216
243
  },
@@ -252,7 +279,8 @@ var SYNAP_PROTO_DESCRIPTOR = {
252
279
  "oneof": [
253
280
  "context_bundle",
254
281
  "heartbeat_pong",
255
- "signal"
282
+ "signal",
283
+ "event_ack"
256
284
  ]
257
285
  }
258
286
  },
@@ -268,6 +296,23 @@ var SYNAP_PROTO_DESCRIPTOR = {
268
296
  "signal": {
269
297
  "type": "StreamSignal",
270
298
  "id": 3
299
+ },
300
+ "event_ack": {
301
+ "type": "EventAck",
302
+ "id": 4
303
+ }
304
+ }
305
+ },
306
+ "EventAck": {
307
+ "fields": {
308
+ "event_ids": {
309
+ "rule": "repeated",
310
+ "type": "string",
311
+ "id": 1
312
+ },
313
+ "timestamp_ms": {
314
+ "type": "int64",
315
+ "id": 2
271
316
  }
272
317
  }
273
318
  },
@@ -394,6 +439,10 @@ var SYNAP_PROTO_DESCRIPTOR = {
394
439
  "ttl_hint_seconds": {
395
440
  "type": "int32",
396
441
  "id": 23
442
+ },
443
+ "anticipation_scope_rung": {
444
+ "type": "string",
445
+ "id": 24
397
446
  }
398
447
  }
399
448
  },
@@ -615,7 +664,66 @@ var BACKOFF_MAX_MS = 3e4;
615
664
  var HEARTBEAT_INTERVAL_MS = 3e4;
616
665
  var HEARTBEAT_TIMEOUT_MS = 1e4;
617
666
  var MAX_MISSED_HEARTBEATS = 3;
667
+ var SEND_QUEUE_MAX_DEPTH = 100;
668
+ var CLOSE_GRACE_MS = 2e3;
669
+ var SEND_QUEUE_MAX_AGE_MS = 5 * 60 * 1e3;
618
670
  var MIN_TTL_SECONDS = 60;
671
+ var GRPC_RESOURCE_EXHAUSTED = 8;
672
+ var CREDIT_ABORT_DETAILS = "insufficient_credits";
673
+ var CREDIT_STOP_REASONS = /* @__PURE__ */ new Set([
674
+ "overages_disabled",
675
+ "trial_limit_reached",
676
+ "subscription_inactive"
677
+ ]);
678
+ function metadataValue(metadata, key) {
679
+ if (metadata === null || typeof metadata !== "object") return null;
680
+ const getter = metadata.get;
681
+ const raw = typeof getter === "function" ? getter.call(metadata, key) : metadata[key];
682
+ const value = Array.isArray(raw) ? raw[0] : raw;
683
+ if (value === void 0 || value === null) return null;
684
+ const text = typeof value === "string" ? value : String(value);
685
+ return text === "" ? null : text;
686
+ }
687
+ function numberOrNull(value) {
688
+ if (value === null) return null;
689
+ const parsed = Number(value);
690
+ return Number.isFinite(parsed) ? parsed : null;
691
+ }
692
+ function creditErrorFromRpc(error, correlationId = null) {
693
+ if (error === null || typeof error !== "object") return null;
694
+ if (error.code !== GRPC_RESOURCE_EXHAUSTED) return null;
695
+ const metadata = error.metadata;
696
+ const reason = metadataValue(metadata, "credit-reason");
697
+ if (reason === null || !CREDIT_STOP_REASONS.has(reason)) return null;
698
+ const rawDetails = error.details;
699
+ const details = typeof rawDetails === "string" && rawDetails !== "" ? rawDetails : CREDIT_ABORT_DETAILS;
700
+ const requestId = _nullishCoalesce(metadataValue(metadata, "request-id"), () => ( correlationId));
701
+ const manageUrl = metadataValue(metadata, "credit-manage-url");
702
+ if (reason === "overages_disabled") {
703
+ return new (0, _chunkT372OL3Qcjs.InsufficientCreditsError)(details, {
704
+ correlationId: requestId,
705
+ balanceCredits: numberOrNull(metadataValue(metadata, "credit-balance")),
706
+ requiredCredits: numberOrNull(metadataValue(metadata, "credit-minimum-required")),
707
+ recoveryUrl: metadataValue(metadata, "credit-recovery-url"),
708
+ redeemUrl: metadataValue(metadata, "credit-redeem-url"),
709
+ reason,
710
+ manageUrl
711
+ });
712
+ }
713
+ return new (0, _chunkT372OL3Qcjs.RateLimitError)(details, {
714
+ correlationId: requestId,
715
+ reason,
716
+ upgradeUrl: metadataValue(metadata, "credit-upgrade-url"),
717
+ manageUrl
718
+ });
719
+ }
720
+ function isCreditStop(error) {
721
+ if (!(error instanceof _chunkT372OL3Qcjs.InsufficientCreditsError) && !(error instanceof _chunkT372OL3Qcjs.RateLimitError)) {
722
+ return false;
723
+ }
724
+ const reason = error.reason;
725
+ return typeof reason === "string" && CREDIT_STOP_REASONS.has(reason);
726
+ }
619
727
  var GrpcStreamClient = class {
620
728
 
621
729
 
@@ -627,6 +735,16 @@ var GrpcStreamClient = class {
627
735
  #missedHeartbeats = 0;
628
736
  #heartbeatTimer = null;
629
737
  #shutdownRequested = false;
738
+ // Events that could not be written because there was no stream.
739
+ #sendQueue = [];
740
+ // Events written to a live stream that the server has not acknowledged. The
741
+ // queue above covers "the stream was down"; this covers the half that
742
+ // actually loses sessions — the write succeeded, the stream broke before the
743
+ // server processed it, and nobody knows.
744
+ #unacked = /* @__PURE__ */ new Map();
745
+ // Why the stream stopped, when it stopped for a reason the caller can act on
746
+ // (today: a credit refusal). Mirrors Python's `last_error`.
747
+ #lastError = null;
630
748
  #grpc = null;
631
749
  #ServiceStub = null;
632
750
  #credentials;
@@ -650,21 +768,149 @@ var GrpcStreamClient = class {
650
768
  get isConnected() {
651
769
  return this.#state === "connected";
652
770
  }
771
+ /**
772
+ * The error that ended the stream, if one did.
773
+ *
774
+ * Set when the server refused the stream for credits, so an `onDisconnect`
775
+ * handler can read the balance, the reason and the URL to fix it. Null for an
776
+ * ordinary disconnect.
777
+ */
778
+ get lastError() {
779
+ return this.#lastError;
780
+ }
653
781
  async connect() {
654
782
  await this.#loadGrpc();
655
783
  this.#shutdownRequested = false;
784
+ this.#lastError = null;
656
785
  await this.#establish();
657
786
  this.#startHeartbeat();
658
787
  }
659
788
  async disconnect() {
660
789
  this.#shutdownRequested = true;
661
790
  this.#stopHeartbeat();
662
- this.#closeCall();
791
+ this.#flushBeforeClose();
792
+ const call = this.#call;
793
+ if (call !== null) {
794
+ try {
795
+ call.end();
796
+ } catch (e) {
797
+ }
798
+ await new Promise((resolve) => {
799
+ let settled = false;
800
+ const done = () => {
801
+ if (!settled) {
802
+ settled = true;
803
+ resolve();
804
+ }
805
+ };
806
+ const timer = setTimeout(() => {
807
+ const left = this.undelivered();
808
+ if (left.unacknowledged > 0 || left.queued > 0) {
809
+ console.warn(
810
+ `[synap] closing with ${left.queued} queued and ${left.unacknowledged} unacknowledged event(s) the server did not confirm within ${CLOSE_GRACE_MS}ms`
811
+ );
812
+ }
813
+ done();
814
+ }, CLOSE_GRACE_MS);
815
+ if (typeof timer.unref === "function") timer.unref();
816
+ call.on("end", () => {
817
+ clearTimeout(timer);
818
+ done();
819
+ });
820
+ call.on("status", () => {
821
+ clearTimeout(timer);
822
+ done();
823
+ });
824
+ call.on("error", () => {
825
+ clearTimeout(timer);
826
+ done();
827
+ });
828
+ });
829
+ }
830
+ this.#call = null;
663
831
  this.#setState("closed");
664
832
  }
665
- /** Fire-and-forget: a dropped event must never fail the caller's turn. */
833
+ /**
834
+ * What this client could not get to the server.
835
+ *
836
+ * Every send path returns void, so a caller had no way to tell a delivered
837
+ * turn from a lost one. Mirrors Python's `undelivered()`.
838
+ */
839
+ undelivered() {
840
+ return { queued: this.#sendQueue.length, unacknowledged: this.#unacked.size };
841
+ }
842
+ /**
843
+ * Never fails the caller's turn, and no longer loses it either.
844
+ *
845
+ * A write that cannot go out is buffered and replayed on reconnect, and a
846
+ * write that does go out is held until the server acknowledges it. Before
847
+ * this, both cases ended at a silent `return`.
848
+ */
666
849
  sendConversationEvent(event) {
667
- this.#write({ conversation_event: { ...event } });
850
+ const stamped = {
851
+ ...event,
852
+ event_id: event.event_id && event.event_id !== "" ? event.event_id : _chunkT372OL3Qcjs.newCorrelationId.call(void 0, ),
853
+ sent_at_ms: _nullishCoalesce(event.sent_at_ms, () => ( Date.now()))
854
+ };
855
+ if (!this.isConnected || this.#call === null) {
856
+ this.#enqueue(stamped);
857
+ return;
858
+ }
859
+ this.#write({ conversation_event: { ...stamped } });
860
+ this.#trackUnacked(stamped);
861
+ }
862
+ #enqueue(event) {
863
+ this.#pruneQueue();
864
+ if (this.#sendQueue.length >= SEND_QUEUE_MAX_DEPTH) {
865
+ const evicted = this.#sendQueue.shift();
866
+ console.warn(
867
+ `[synap] send queue full (max=${SEND_QUEUE_MAX_DEPTH}); dropping oldest event (event_type=${_optionalChain([evicted, 'optionalAccess', _ => _.event, 'access', _2 => _2.event_type])})`
868
+ );
869
+ }
870
+ this.#sendQueue.push({ queuedAt: Date.now(), event });
871
+ }
872
+ #pruneQueue() {
873
+ const cutoff = Date.now() - SEND_QUEUE_MAX_AGE_MS;
874
+ while (this.#sendQueue.length > 0 && (_nullishCoalesce(_optionalChain([this, 'access', _3 => _3.#sendQueue, 'access', _4 => _4[0], 'optionalAccess', _5 => _5.queuedAt]), () => ( 0))) < cutoff) {
875
+ const expired = this.#sendQueue.shift();
876
+ console.warn(
877
+ `[synap] queued event expired (age>${SEND_QUEUE_MAX_AGE_MS}ms); dropping (event_type=${_optionalChain([expired, 'optionalAccess', _6 => _6.event, 'access', _7 => _7.event_type])})`
878
+ );
879
+ }
880
+ }
881
+ #trackUnacked(event) {
882
+ const id = event.event_id;
883
+ if (id === void 0 || id === "") return;
884
+ const cutoff = Date.now() - SEND_QUEUE_MAX_AGE_MS;
885
+ for (const [oldestId, held] of this.#unacked) {
886
+ if (held.queuedAt >= cutoff && this.#unacked.size < SEND_QUEUE_MAX_DEPTH) break;
887
+ this.#unacked.delete(oldestId);
888
+ console.warn(
889
+ `[synap] unacknowledged event dropped: the server never confirmed it (event_type=${held.event.event_type})`
890
+ );
891
+ }
892
+ this.#unacked.set(id, { queuedAt: Date.now(), event });
893
+ }
894
+ /**
895
+ * Replay what the server never confirmed, then what never went out.
896
+ *
897
+ * That order is the order things happened in: an unacked event was written
898
+ * to the previous stream, so it is older than anything that piled up while
899
+ * there was no stream at all. Safe to repeat — every event carries the id it
900
+ * was first sent with and the server drops a second sighting of it.
901
+ */
902
+ #replayBuffered() {
903
+ const unacked = [...this.#unacked.values()];
904
+ const queued = this.#sendQueue;
905
+ this.#sendQueue = [];
906
+ for (const held of [...unacked, ...queued]) {
907
+ this.#write({ conversation_event: { ...held.event } });
908
+ }
909
+ if (unacked.length > 0 || queued.length > 0) {
910
+ console.warn(
911
+ `[synap] replayed ${unacked.length} unacknowledged and ${queued.length} queued event(s) after reconnect`
912
+ );
913
+ }
668
914
  }
669
915
  sendSessionControl(input) {
670
916
  this.#write({ session_control: { ...input } });
@@ -675,11 +921,26 @@ var GrpcStreamClient = class {
675
921
  sendContextAssembled(event) {
676
922
  this.#write({ context_assembled: { ...event } });
677
923
  }
924
+ #flushBeforeClose() {
925
+ if (!this.isConnected || this.#call === null) {
926
+ if (this.#sendQueue.length > 0 || this.#unacked.size > 0) {
927
+ console.warn(
928
+ `[synap] closing with ${this.#sendQueue.length} queued and ${this.#unacked.size} unacknowledged event(s) and no stream to send them on; they are lost`
929
+ );
930
+ }
931
+ return;
932
+ }
933
+ const queued = this.#sendQueue;
934
+ this.#sendQueue = [];
935
+ for (const held of queued) {
936
+ this.#write({ conversation_event: { ...held.event } });
937
+ }
938
+ }
678
939
  #write(msg) {
679
940
  if (!this.isConnected || this.#call === null) return;
680
941
  try {
681
942
  this.#call.write(msg);
682
- } catch (e) {
943
+ } catch (e2) {
683
944
  }
684
945
  }
685
946
  async #loadGrpc() {
@@ -732,6 +993,11 @@ var GrpcStreamClient = class {
732
993
  const call = stub["Listen"](metadata);
733
994
  call.on("data", (msg) => this.#handleMessage(msg));
734
995
  call.on("error", (err) => {
996
+ const creditError = creditErrorFromRpc(err);
997
+ if (creditError !== null) {
998
+ this.#handleCreditStop(creditError);
999
+ return;
1000
+ }
735
1001
  void this.#handleDisconnect(`error: ${String(err)}`);
736
1002
  });
737
1003
  call.on("end", () => {
@@ -741,8 +1007,14 @@ var GrpcStreamClient = class {
741
1007
  this.#setState("connected");
742
1008
  this.#reconnectAttempts = 0;
743
1009
  this.#lastPongAt = Date.now();
1010
+ this.#replayBuffered();
744
1011
  }
745
1012
  #handleMessage(msg) {
1013
+ const ack = msg["event_ack"];
1014
+ if (ack) {
1015
+ for (const id of _nullishCoalesce(ack.event_ids, () => ( []))) this.#unacked.delete(id);
1016
+ return;
1017
+ }
746
1018
  if (msg["context_bundle"]) {
747
1019
  this.#handleBundle(msg["context_bundle"]);
748
1020
  return;
@@ -753,7 +1025,7 @@ var GrpcStreamClient = class {
753
1025
  return;
754
1026
  }
755
1027
  const signal = msg["signal"];
756
- if (_optionalChain([signal, 'optionalAccess', _ => _.signal_type]) === "closing") {
1028
+ if (_optionalChain([signal, 'optionalAccess', _8 => _8.signal_type]) === "closing") {
757
1029
  void this.#handleDisconnect("server_signal_closing");
758
1030
  }
759
1031
  }
@@ -762,14 +1034,14 @@ var GrpcStreamClient = class {
762
1034
  const conversationId = String(_nullishCoalesce(bundle["anticipation_conversation_id"], () => ( "")));
763
1035
  if (bundleType !== UNCACHEABLE_BUNDLE_TYPE) this.#store(bundle, bundleType, conversationId);
764
1036
  if (bundleType === "compaction_update" && conversationId !== "") {
765
- _optionalChain([this, 'access', _2 => _2.#options, 'access', _3 => _3.onCompactionUpdate, 'optionalCall', _4 => _4(conversationId, bundle)]);
1037
+ _optionalChain([this, 'access', _9 => _9.#options, 'access', _10 => _10.onCompactionUpdate, 'optionalCall', _11 => _11(conversationId, bundle)]);
766
1038
  }
767
- _optionalChain([this, 'access', _5 => _5.#options, 'access', _6 => _6.onContext, 'optionalCall', _7 => _7(bundle)]);
1039
+ _optionalChain([this, 'access', _12 => _12.#options, 'access', _13 => _13.onContext, 'optionalCall', _14 => _14(bundle)]);
768
1040
  }
769
1041
  #store(bundle, bundleType, conversationId) {
770
1042
  const itemsByType = {};
771
1043
  for (const [type, list] of Object.entries(_nullishCoalesce(bundle["items_by_type"], () => ( {})))) {
772
- const items = _optionalChain([list, 'optionalAccess', _8 => _8.items]);
1044
+ const items = _optionalChain([list, 'optionalAccess', _15 => _15.items]);
773
1045
  itemsByType[type] = _nullishCoalesce(items, () => ( []));
774
1046
  }
775
1047
  const ttlHint = Number(_nullishCoalesce(bundle["ttl_hint_seconds"], () => ( 0)));
@@ -780,6 +1052,10 @@ var GrpcStreamClient = class {
780
1052
  // customer; falling back to customer id keeps org-scope bundles usable.
781
1053
  entityId: String(_nullishCoalesce(bundle["anticipation_user_id"], () => ( ""))) || String(_nullishCoalesce(bundle["anticipation_customer_id"], () => ( ""))) || null,
782
1054
  conversationId: conversationId || null,
1055
+ // The rung the server retrieved this bundle at. Absent from anything an
1056
+ // older server sent, and absent reads as "named no rung" rather than as
1057
+ // "matches anything".
1058
+ scopeRung: String(_nullishCoalesce(bundle["anticipation_scope_rung"], () => ( ""))) || null,
783
1059
  itemsByType,
784
1060
  // 0 means "use the SDK default"; anything else is floored at 60s.
785
1061
  ttlHintSeconds: ttlHint > 0 ? Math.max(ttlHint, MIN_TTL_SECONDS) : null,
@@ -790,6 +1066,20 @@ var GrpcStreamClient = class {
790
1066
  ]
791
1067
  });
792
1068
  }
1069
+ /**
1070
+ * End the stream because the server refused it for credits.
1071
+ *
1072
+ * Reconnecting cannot clear a credit refusal, so the stream closes and the
1073
+ * typed error is kept on `lastError` for the application to read. Mirrors
1074
+ * Python's `_handle_credit_stop`.
1075
+ */
1076
+ #handleCreditStop(error) {
1077
+ this.#lastError = error;
1078
+ this.#shutdownRequested = true;
1079
+ this.#stopHeartbeat();
1080
+ this.#closeCall();
1081
+ this.#setState("disconnected");
1082
+ }
793
1083
  async #handleDisconnect(_reason) {
794
1084
  if (this.#shutdownRequested) return;
795
1085
  this.#closeCall();
@@ -807,7 +1097,11 @@ var GrpcStreamClient = class {
807
1097
  if (this.#shutdownRequested) return;
808
1098
  try {
809
1099
  await this.#establish();
810
- } catch (e2) {
1100
+ } catch (error) {
1101
+ if (isCreditStop(error)) {
1102
+ this.#handleCreditStop(error);
1103
+ return;
1104
+ }
811
1105
  void this.#handleDisconnect("reconnect_failed");
812
1106
  }
813
1107
  }
@@ -828,7 +1122,7 @@ var GrpcStreamClient = class {
828
1122
  void this.#handleDisconnect("heartbeat_write_error");
829
1123
  }
830
1124
  }, HEARTBEAT_INTERVAL_MS);
831
- _optionalChain([this, 'access', _9 => _9.#heartbeatTimer, 'access', _10 => _10.unref, 'optionalCall', _11 => _11()]);
1125
+ _optionalChain([this, 'access', _16 => _16.#heartbeatTimer, 'access', _17 => _17.unref, 'optionalCall', _18 => _18()]);
832
1126
  }
833
1127
  #stopHeartbeat() {
834
1128
  if (this.#heartbeatTimer !== null) {
@@ -838,19 +1132,23 @@ var GrpcStreamClient = class {
838
1132
  }
839
1133
  #closeCall() {
840
1134
  try {
841
- _optionalChain([this, 'access', _12 => _12.#call, 'optionalAccess', _13 => _13.end, 'call', _14 => _14()]);
1135
+ _optionalChain([this, 'access', _19 => _19.#call, 'optionalAccess', _20 => _20.end, 'call', _21 => _21()]);
842
1136
  } catch (e4) {
843
1137
  }
844
1138
  this.#call = null;
845
1139
  }
846
1140
  #setState(state) {
847
1141
  this.#state = state;
848
- _optionalChain([this, 'access', _15 => _15.#options, 'access', _16 => _16.onStateChange, 'optionalCall', _17 => _17(state)]);
1142
+ _optionalChain([this, 'access', _22 => _22.#options, 'access', _23 => _23.onStateChange, 'optionalCall', _24 => _24(state)]);
849
1143
  }
850
1144
  };
851
1145
 
852
1146
 
853
1147
 
854
1148
 
855
- exports.PROTO_SHA = PROTO_SHA; exports.GrpcStreamClient = GrpcStreamClient;
856
- //# sourceMappingURL=chunk-3JSE64GW.cjs.map
1149
+
1150
+
1151
+
1152
+
1153
+ exports.PROTO_SHA = PROTO_SHA; exports.CREDIT_ABORT_DETAILS = CREDIT_ABORT_DETAILS; exports.CREDIT_STOP_REASONS = CREDIT_STOP_REASONS; exports.creditErrorFromRpc = creditErrorFromRpc; exports.isCreditStop = isCreditStop; exports.GrpcStreamClient = GrpcStreamClient;
1154
+ //# sourceMappingURL=chunk-IA2N5NUY.cjs.map