@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.
- package/dist/anticipation-cache-LRE3TLK4.cjs +14 -0
- package/dist/anticipation-cache-LRE3TLK4.cjs.map +1 -0
- package/dist/anticipation-cache-WLU4NMD5.js +14 -0
- package/dist/{chunk-ZU6ZC4SU.js → chunk-ASM2OIIG.js} +305 -7
- package/dist/chunk-ASM2OIIG.js.map +1 -0
- package/dist/{chunk-OCMM2Z5P.js → chunk-BWMPKLFG.js} +40 -2
- package/dist/chunk-BWMPKLFG.js.map +1 -0
- package/dist/{chunk-3JSE64GW.cjs → chunk-IA2N5NUY.cjs} +315 -17
- package/dist/chunk-IA2N5NUY.cjs.map +1 -0
- package/dist/{chunk-6LDUDKHS.js → chunk-ODVOQOSO.js} +124 -8
- package/dist/chunk-ODVOQOSO.js.map +1 -0
- package/dist/{chunk-2TVL5IPQ.cjs → chunk-T372OL3Q.cjs} +41 -3
- package/dist/chunk-T372OL3Q.cjs.map +1 -0
- package/dist/{chunk-RB57I2IQ.cjs → chunk-T6U5PVAD.cjs} +125 -9
- package/dist/chunk-T6U5PVAD.cjs.map +1 -0
- package/dist/chunk-ZKOJDUN4.cjs.map +1 -1
- package/dist/env-55ULHNJZ.cjs.map +1 -1
- package/dist/errors-Bl3vY1eb.d.cts +382 -0
- package/dist/errors-Bl3vY1eb.d.ts +382 -0
- package/dist/grpc/index.cjs +8 -8
- package/dist/grpc/index.cjs.map +1 -1
- package/dist/grpc/index.d.cts +44 -4
- package/dist/grpc/index.d.ts +44 -4
- package/dist/grpc/index.js +6 -6
- package/dist/index.cjs +301 -173
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -146
- package/dist/index.d.ts +102 -146
- package/dist/index.js +192 -64
- package/dist/index.js.map +1 -1
- package/dist/stream-client-6OWRYOA4.js +16 -0
- package/dist/stream-client-Y5JGSF2R.cjs +16 -0
- package/dist/stream-client-Y5JGSF2R.cjs.map +1 -0
- package/package.json +6 -1
- package/dist/anticipation-cache-ARYEEKMU.cjs +0 -10
- package/dist/anticipation-cache-ARYEEKMU.cjs.map +0 -1
- package/dist/anticipation-cache-BMVF5sXb.d.cts +0 -190
- package/dist/anticipation-cache-BMVF5sXb.d.ts +0 -190
- package/dist/anticipation-cache-H7HEVWQC.js +0 -10
- package/dist/chunk-2TVL5IPQ.cjs.map +0 -1
- package/dist/chunk-3JSE64GW.cjs.map +0 -1
- package/dist/chunk-6LDUDKHS.js.map +0 -1
- package/dist/chunk-OCMM2Z5P.js.map +0 -1
- package/dist/chunk-RB57I2IQ.cjs.map +0 -1
- package/dist/chunk-ZU6ZC4SU.js.map +0 -1
- package/dist/stream-client-6V4FGKZ3.js +0 -7
- package/dist/stream-client-PRTOH62B.cjs +0 -7
- package/dist/stream-client-PRTOH62B.cjs.map +0 -1
- /package/dist/{anticipation-cache-H7HEVWQC.js.map → anticipation-cache-WLU4NMD5.js.map} +0 -0
- /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-
|
|
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; }
|
|
2
|
-
|
|
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.#
|
|
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
|
-
/**
|
|
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
|
-
|
|
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 (
|
|
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',
|
|
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',
|
|
1037
|
+
_optionalChain([this, 'access', _9 => _9.#options, 'access', _10 => _10.onCompactionUpdate, 'optionalCall', _11 => _11(conversationId, bundle)]);
|
|
766
1038
|
}
|
|
767
|
-
_optionalChain([this, 'access',
|
|
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',
|
|
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 (
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
-
|
|
856
|
-
|
|
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
|