@amigo-ai/platform-sdk 0.24.0 → 0.26.0
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/api.md +6 -3
- package/dist/core/errors.js +155 -9
- package/dist/core/errors.js.map +1 -1
- package/dist/core/reconnecting-websocket.js +371 -0
- package/dist/core/reconnecting-websocket.js.map +1 -0
- package/dist/index.cjs +711 -13
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +711 -13
- package/dist/index.mjs.map +4 -4
- package/dist/resources/events.js +139 -3
- package/dist/resources/events.js.map +1 -1
- package/dist/resources/integrations.js +25 -0
- package/dist/resources/integrations.js.map +1 -1
- package/dist/resources/observers.js +238 -0
- package/dist/resources/observers.js.map +1 -0
- package/dist/types/core/errors.d.ts +93 -1
- package/dist/types/core/errors.d.ts.map +1 -1
- package/dist/types/core/reconnecting-websocket.d.ts +156 -0
- package/dist/types/core/reconnecting-websocket.d.ts.map +1 -0
- package/dist/types/generated/api.d.ts +686 -113
- package/dist/types/generated/api.d.ts.map +1 -1
- package/dist/types/index.d.cts +42 -2
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +42 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/resources/events.d.ts +33 -0
- package/dist/types/resources/events.d.ts.map +1 -1
- package/dist/types/resources/functions.d.ts.map +1 -1
- package/dist/types/resources/integrations.d.ts +33 -0
- package/dist/types/resources/integrations.d.ts.map +1 -1
- package/dist/types/resources/metrics.d.ts.map +1 -1
- package/dist/types/resources/observers.d.ts +148 -0
- package/dist/types/resources/observers.d.ts.map +1 -0
- package/dist/types/resources/operators.d.ts.map +1 -1
- package/dist/types/resources/settings.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -44,9 +44,11 @@ __export(index_exports, {
|
|
|
44
44
|
MemoryTokenStorage: () => MemoryTokenStorage,
|
|
45
45
|
NetworkError: () => NetworkError,
|
|
46
46
|
NotFoundError: () => NotFoundError,
|
|
47
|
+
ObserversResource: () => ObserversResource,
|
|
47
48
|
ParseError: () => ParseError,
|
|
48
49
|
PermissionError: () => PermissionError,
|
|
49
50
|
RateLimitError: () => RateLimitError,
|
|
51
|
+
ReconnectingWebSocketError: () => ReconnectingWebSocketError,
|
|
50
52
|
RefreshTokenExpiredError: () => RefreshTokenExpiredError,
|
|
51
53
|
RequestTimeoutError: () => RequestTimeoutError,
|
|
52
54
|
ServerError: () => ServerError,
|
|
@@ -54,12 +56,14 @@ __export(index_exports, {
|
|
|
54
56
|
TokenManager: () => TokenManager,
|
|
55
57
|
ValidationError: () => ValidationError,
|
|
56
58
|
WebhookVerificationError: () => WebhookVerificationError,
|
|
59
|
+
WorkspaceEventStreamError: () => WorkspaceEventStreamError,
|
|
57
60
|
actionId: () => actionId,
|
|
58
61
|
agentId: () => agentId,
|
|
59
62
|
apiKeyId: () => apiKeyId,
|
|
60
63
|
buildLastResponse: () => buildLastResponse,
|
|
61
64
|
callId: () => callId,
|
|
62
65
|
contextGraphId: () => contextGraphId,
|
|
66
|
+
createReconnectingWebSocket: () => createReconnectingWebSocket,
|
|
63
67
|
dataSourceId: () => dataSourceId,
|
|
64
68
|
entityId: () => entityId,
|
|
65
69
|
eventId: () => eventId,
|
|
@@ -71,10 +75,20 @@ __export(index_exports, {
|
|
|
71
75
|
integrationId: () => integrationId,
|
|
72
76
|
isAmigoError: () => isAmigoError,
|
|
73
77
|
isAuthenticationError: () => isAuthenticationError,
|
|
78
|
+
isConflictError: () => isConflictError,
|
|
79
|
+
isHttpException: () => isHttpException,
|
|
80
|
+
isHttpValidationError: () => isHttpValidationError,
|
|
81
|
+
isNetworkError: () => isNetworkError,
|
|
74
82
|
isNotFoundError: () => isNotFoundError,
|
|
83
|
+
isPermissionError: () => isPermissionError,
|
|
75
84
|
isRateLimitError: () => isRateLimitError,
|
|
76
85
|
isRequestTimeoutError: () => isRequestTimeoutError,
|
|
86
|
+
isServerError: () => isServerError,
|
|
87
|
+
isUnparseableErrorBody: () => isUnparseableErrorBody,
|
|
88
|
+
isValidationError: () => isValidationError,
|
|
89
|
+
isWorkspaceEventStreamError: () => isWorkspaceEventStreamError,
|
|
77
90
|
loginWithDeviceCode: () => loginWithDeviceCode,
|
|
91
|
+
observerAuthProtocols: () => observerAuthProtocols,
|
|
78
92
|
openBrowser: () => openBrowser,
|
|
79
93
|
paginate: () => paginate,
|
|
80
94
|
parseRateLimitHeaders: () => parseRateLimitHeaders,
|
|
@@ -93,6 +107,7 @@ __export(index_exports, {
|
|
|
93
107
|
module.exports = __toCommonJS(index_exports);
|
|
94
108
|
|
|
95
109
|
// src/core/errors.ts
|
|
110
|
+
var RAW_BODY_LIMIT = 8 * 1024;
|
|
96
111
|
var SENSITIVE_FIELDS = /* @__PURE__ */ new Set([
|
|
97
112
|
"id_token",
|
|
98
113
|
"access_token",
|
|
@@ -128,6 +143,20 @@ var AmigoError = class extends Error {
|
|
|
128
143
|
requestId;
|
|
129
144
|
detail;
|
|
130
145
|
context;
|
|
146
|
+
/**
|
|
147
|
+
* Typed body of the error response, when one was returned and successfully
|
|
148
|
+
* parsed. Use the `isHttpException` / `isHttpValidationError` /
|
|
149
|
+
* `isUnparseableErrorBody` type guards to narrow the discriminated union.
|
|
150
|
+
*
|
|
151
|
+
* Named `errorBody` (not `body`) to avoid colliding with the legacy
|
|
152
|
+
* `ParseError.body: string` field.
|
|
153
|
+
*/
|
|
154
|
+
errorBody;
|
|
155
|
+
/**
|
|
156
|
+
* Raw response body (truncated to 8 KB). Populated even when parsing fails,
|
|
157
|
+
* so callers always have something to log when debugging server errors.
|
|
158
|
+
*/
|
|
159
|
+
rawBody;
|
|
131
160
|
constructor(message, ctx = {}) {
|
|
132
161
|
super(message);
|
|
133
162
|
this.name = this.constructor.name;
|
|
@@ -136,6 +165,8 @@ var AmigoError = class extends Error {
|
|
|
136
165
|
this.requestId = ctx.requestId;
|
|
137
166
|
this.detail = ctx.detail;
|
|
138
167
|
this.context = ctx.context ? sanitizeErrorContext(ctx.context) : void 0;
|
|
168
|
+
this.errorBody = ctx.errorBody;
|
|
169
|
+
this.rawBody = ctx.rawBody;
|
|
139
170
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
140
171
|
if (typeof Error.captureStackTrace === "function") {
|
|
141
172
|
Error.captureStackTrace(this, this.constructor);
|
|
@@ -228,21 +259,69 @@ var ConfigurationError = class extends AmigoError {
|
|
|
228
259
|
super(message);
|
|
229
260
|
}
|
|
230
261
|
};
|
|
231
|
-
async function
|
|
232
|
-
let
|
|
262
|
+
async function readErrorBody(response) {
|
|
263
|
+
let rawBody = "";
|
|
233
264
|
try {
|
|
234
|
-
|
|
235
|
-
body = JSON.parse(rawBody);
|
|
265
|
+
rawBody = await response.text();
|
|
236
266
|
} catch {
|
|
267
|
+
return {
|
|
268
|
+
body: { detail: response.statusText || `HTTP ${response.status}`, raw_body: "" },
|
|
269
|
+
rawBody: ""
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const truncatedRaw = rawBody.length > RAW_BODY_LIMIT ? rawBody.slice(0, RAW_BODY_LIMIT) : rawBody;
|
|
273
|
+
if (rawBody.length === 0) {
|
|
274
|
+
return {
|
|
275
|
+
body: { detail: response.statusText || `HTTP ${response.status}`, raw_body: "" },
|
|
276
|
+
rawBody: ""
|
|
277
|
+
};
|
|
237
278
|
}
|
|
279
|
+
try {
|
|
280
|
+
const parsed = JSON.parse(rawBody);
|
|
281
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
282
|
+
return { body: parsed, rawBody: truncatedRaw };
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
body: {
|
|
286
|
+
detail: response.statusText || `HTTP ${response.status}`,
|
|
287
|
+
raw_body: truncatedRaw
|
|
288
|
+
},
|
|
289
|
+
rawBody: truncatedRaw
|
|
290
|
+
};
|
|
291
|
+
} catch {
|
|
292
|
+
return {
|
|
293
|
+
body: {
|
|
294
|
+
detail: response.statusText || `HTTP ${response.status}`,
|
|
295
|
+
raw_body: truncatedRaw
|
|
296
|
+
},
|
|
297
|
+
rawBody: truncatedRaw
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function safeStringify(value) {
|
|
302
|
+
try {
|
|
303
|
+
return JSON.stringify(value);
|
|
304
|
+
} catch {
|
|
305
|
+
return String(value);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function createApiError(response) {
|
|
309
|
+
const { body, rawBody } = await readErrorBody(response);
|
|
310
|
+
const flat = body;
|
|
311
|
+
const detailString = typeof flat.detail === "string" ? flat.detail : flat.detail !== void 0 ? safeStringify(flat.detail) : void 0;
|
|
312
|
+
const errorCode = typeof flat.error_code === "string" ? flat.error_code : void 0;
|
|
313
|
+
const requestIdFromBody = typeof flat.request_id === "string" ? flat.request_id : void 0;
|
|
314
|
+
const messageFromBody = typeof flat.message === "string" ? flat.message : void 0;
|
|
238
315
|
const ctx = {
|
|
239
316
|
statusCode: response.status,
|
|
240
|
-
errorCode
|
|
241
|
-
requestId:
|
|
242
|
-
detail:
|
|
243
|
-
context: { url: response.url, response: body }
|
|
317
|
+
errorCode,
|
|
318
|
+
requestId: requestIdFromBody ?? response.headers.get("x-request-id") ?? void 0,
|
|
319
|
+
detail: detailString,
|
|
320
|
+
context: { url: response.url, response: body },
|
|
321
|
+
errorBody: body,
|
|
322
|
+
rawBody
|
|
244
323
|
};
|
|
245
|
-
const message =
|
|
324
|
+
const message = messageFromBody ?? detailString ?? response.statusText ?? `HTTP ${response.status}`;
|
|
246
325
|
switch (response.status) {
|
|
247
326
|
case 400:
|
|
248
327
|
return new BadRequestError(message, ctx);
|
|
@@ -292,6 +371,40 @@ function isAuthenticationError(err) {
|
|
|
292
371
|
function isRequestTimeoutError(err) {
|
|
293
372
|
return err instanceof RequestTimeoutError;
|
|
294
373
|
}
|
|
374
|
+
function isPermissionError(err) {
|
|
375
|
+
return err instanceof PermissionError;
|
|
376
|
+
}
|
|
377
|
+
function isConflictError(err) {
|
|
378
|
+
return err instanceof ConflictError;
|
|
379
|
+
}
|
|
380
|
+
function isValidationError(err) {
|
|
381
|
+
return err instanceof ValidationError;
|
|
382
|
+
}
|
|
383
|
+
function isServerError(err) {
|
|
384
|
+
return err instanceof ServerError;
|
|
385
|
+
}
|
|
386
|
+
function isNetworkError(err) {
|
|
387
|
+
return err instanceof NetworkError;
|
|
388
|
+
}
|
|
389
|
+
function isHttpValidationError(err) {
|
|
390
|
+
if (!(err instanceof AmigoError)) return false;
|
|
391
|
+
const body = err.errorBody;
|
|
392
|
+
return Array.isArray(body?.detail);
|
|
393
|
+
}
|
|
394
|
+
function isHttpException(err) {
|
|
395
|
+
if (!(err instanceof AmigoError)) return false;
|
|
396
|
+
const body = err.errorBody;
|
|
397
|
+
if (!body) return false;
|
|
398
|
+
if (Array.isArray(body.detail)) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
return "detail" in body && !("raw_body" in body);
|
|
402
|
+
}
|
|
403
|
+
function isUnparseableErrorBody(err) {
|
|
404
|
+
if (!(err instanceof AmigoError)) return false;
|
|
405
|
+
const body = err.errorBody;
|
|
406
|
+
return typeof body?.raw_body === "string";
|
|
407
|
+
}
|
|
295
408
|
|
|
296
409
|
// src/core/openapi-client.ts
|
|
297
410
|
var import_openapi_fetch = __toESM(require("openapi-fetch"), 1);
|
|
@@ -2455,6 +2568,36 @@ var IntegrationsResource = class extends WorkspaceScopedResource {
|
|
|
2455
2568
|
)
|
|
2456
2569
|
);
|
|
2457
2570
|
}
|
|
2571
|
+
/**
|
|
2572
|
+
* Probe an integration's connection + auth without invoking any specific
|
|
2573
|
+
* endpoint. Exercises auth resolution end-to-end (SSM lookups, OAuth2 token
|
|
2574
|
+
* mints, JWT signing) and sends a HEAD request to ``base_url`` (REST/FHIR)
|
|
2575
|
+
* or ``mcp_url`` (MCP). Safe on production integrations — HEAD carries no
|
|
2576
|
+
* side effects.
|
|
2577
|
+
*
|
|
2578
|
+
* The most recent probe outcome is persisted on the integration so
|
|
2579
|
+
* subsequent ``get`` / ``list`` responses surface ``last_tested_at`` +
|
|
2580
|
+
* ``last_test_status`` without re-probing.
|
|
2581
|
+
*
|
|
2582
|
+
* @returns ``status`` is one of ``healthy`` / ``auth_failed`` /
|
|
2583
|
+
* ``unreachable`` / ``timeout`` / ``ssl_error`` / ``misconfigured``,
|
|
2584
|
+
* each mapping to a distinct, actionable user message.
|
|
2585
|
+
*/
|
|
2586
|
+
async testConnection(integrationId2) {
|
|
2587
|
+
return extractData(
|
|
2588
|
+
await this.client.POST(
|
|
2589
|
+
"/v1/{workspace_id}/integrations/{integration_id}/test-connection",
|
|
2590
|
+
{
|
|
2591
|
+
params: {
|
|
2592
|
+
path: {
|
|
2593
|
+
workspace_id: this.workspaceId,
|
|
2594
|
+
integration_id: integrationId2
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
)
|
|
2599
|
+
);
|
|
2600
|
+
}
|
|
2458
2601
|
/** Check health of all integrations in the workspace */
|
|
2459
2602
|
async getHealthCheck() {
|
|
2460
2603
|
return extractData(
|
|
@@ -3345,6 +3488,22 @@ var ComplianceResource = class extends WorkspaceScopedResource {
|
|
|
3345
3488
|
};
|
|
3346
3489
|
|
|
3347
3490
|
// src/resources/events.ts
|
|
3491
|
+
var WorkspaceEventStreamError = class extends Error {
|
|
3492
|
+
code;
|
|
3493
|
+
retryable;
|
|
3494
|
+
/** Raw decoded ``error`` frame body (or ``undefined`` for transport errors). */
|
|
3495
|
+
frame;
|
|
3496
|
+
constructor(message, code, retryable, frame) {
|
|
3497
|
+
super(message);
|
|
3498
|
+
this.name = "WorkspaceEventStreamError";
|
|
3499
|
+
this.code = code;
|
|
3500
|
+
this.retryable = retryable;
|
|
3501
|
+
this.frame = frame;
|
|
3502
|
+
}
|
|
3503
|
+
};
|
|
3504
|
+
function isWorkspaceEventStreamError(value) {
|
|
3505
|
+
return value instanceof WorkspaceEventStreamError;
|
|
3506
|
+
}
|
|
3348
3507
|
var DEFAULT_INITIAL_DELAY_MS = 3e3;
|
|
3349
3508
|
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
3350
3509
|
var DEFAULT_MAX_RECONNECTS = 10;
|
|
@@ -3396,6 +3555,13 @@ var EventsResource = class extends WorkspaceScopedResource {
|
|
|
3396
3555
|
};
|
|
3397
3556
|
}
|
|
3398
3557
|
};
|
|
3558
|
+
var TERMINAL_SERVER_ERROR_CODES = {
|
|
3559
|
+
too_many_streams: "too_many_streams"
|
|
3560
|
+
};
|
|
3561
|
+
var RECOVERABLE_SERVER_ERROR_CODES = {
|
|
3562
|
+
stream_unavailable: "stream_unavailable",
|
|
3563
|
+
stream_error: "stream_error"
|
|
3564
|
+
};
|
|
3399
3565
|
async function runSubscription(client, workspaceId2, options, signal) {
|
|
3400
3566
|
let lastEventId = options.lastEventId;
|
|
3401
3567
|
let attempt = 0;
|
|
@@ -3444,10 +3610,23 @@ async function runSubscription(client, workspaceId2, options, signal) {
|
|
|
3444
3610
|
reportError(outcome.error);
|
|
3445
3611
|
return;
|
|
3446
3612
|
}
|
|
3613
|
+
if (outcome.kind === "terminal-server-error") {
|
|
3614
|
+
reportError(
|
|
3615
|
+
new WorkspaceEventStreamError(
|
|
3616
|
+
outcome.message,
|
|
3617
|
+
outcome.code,
|
|
3618
|
+
outcome.retryable,
|
|
3619
|
+
outcome.frame
|
|
3620
|
+
)
|
|
3621
|
+
);
|
|
3622
|
+
return;
|
|
3623
|
+
}
|
|
3447
3624
|
if (attempt >= maxReconnects) {
|
|
3448
3625
|
reportError(
|
|
3449
|
-
new
|
|
3450
|
-
`SSE subscription exhausted reconnect budget (${maxReconnects}): ${outcome.reason}
|
|
3626
|
+
new WorkspaceEventStreamError(
|
|
3627
|
+
`SSE subscription exhausted reconnect budget (${maxReconnects}): ${outcome.reason}`,
|
|
3628
|
+
"transport_exhausted",
|
|
3629
|
+
true
|
|
3451
3630
|
)
|
|
3452
3631
|
);
|
|
3453
3632
|
return;
|
|
@@ -3482,7 +3661,7 @@ async function runOneConnection(args) {
|
|
|
3482
3661
|
return { kind: "transport-error", reason: error.message };
|
|
3483
3662
|
}
|
|
3484
3663
|
if (result.error !== void 0) {
|
|
3485
|
-
return { kind: "transport-error", reason: `API error: ${
|
|
3664
|
+
return { kind: "transport-error", reason: `API error: ${safeStringify2(result.error)}` };
|
|
3486
3665
|
}
|
|
3487
3666
|
const body = result.data;
|
|
3488
3667
|
if (!(body instanceof ReadableStream)) {
|
|
@@ -3497,6 +3676,19 @@ async function runOneConnection(args) {
|
|
|
3497
3676
|
if (frame.id !== void 0) {
|
|
3498
3677
|
args.onIdAdvance(frame.id);
|
|
3499
3678
|
}
|
|
3679
|
+
if (frame.event === "error" && frame.data !== void 0) {
|
|
3680
|
+
const errOutcome = interpretServerErrorFrame(frame.data);
|
|
3681
|
+
if (errOutcome.terminal) {
|
|
3682
|
+
return {
|
|
3683
|
+
kind: "terminal-server-error",
|
|
3684
|
+
code: errOutcome.code,
|
|
3685
|
+
message: errOutcome.message,
|
|
3686
|
+
retryable: errOutcome.retryable,
|
|
3687
|
+
frame: errOutcome.frame
|
|
3688
|
+
};
|
|
3689
|
+
}
|
|
3690
|
+
return { kind: "transport-error", reason: errOutcome.message };
|
|
3691
|
+
}
|
|
3500
3692
|
if (frame.event && frame.data !== void 0) {
|
|
3501
3693
|
const event = parseWorkspaceFrame(frame.event, frame.data);
|
|
3502
3694
|
if (event) {
|
|
@@ -3620,12 +3812,63 @@ function parseWorkspaceFrame(eventName, dataJson) {
|
|
|
3620
3812
|
event_type: eventName
|
|
3621
3813
|
};
|
|
3622
3814
|
}
|
|
3815
|
+
function interpretServerErrorFrame(dataJson) {
|
|
3816
|
+
let payload;
|
|
3817
|
+
try {
|
|
3818
|
+
payload = JSON.parse(dataJson);
|
|
3819
|
+
} catch {
|
|
3820
|
+
return {
|
|
3821
|
+
terminal: false,
|
|
3822
|
+
code: "stream_error",
|
|
3823
|
+
message: "Server sent malformed error frame",
|
|
3824
|
+
retryable: true,
|
|
3825
|
+
frame: {}
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
3829
|
+
return {
|
|
3830
|
+
terminal: false,
|
|
3831
|
+
code: "stream_error",
|
|
3832
|
+
message: "Server sent non-object error frame",
|
|
3833
|
+
retryable: true,
|
|
3834
|
+
frame: {}
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
const obj = payload;
|
|
3838
|
+
const rawCode = typeof obj["code"] === "string" ? obj["code"] : "";
|
|
3839
|
+
const message = typeof obj["message"] === "string" ? obj["message"] : "Stream error";
|
|
3840
|
+
if (rawCode in TERMINAL_SERVER_ERROR_CODES) {
|
|
3841
|
+
return {
|
|
3842
|
+
terminal: true,
|
|
3843
|
+
code: TERMINAL_SERVER_ERROR_CODES[rawCode],
|
|
3844
|
+
message,
|
|
3845
|
+
retryable: false,
|
|
3846
|
+
frame: obj
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
if (rawCode in RECOVERABLE_SERVER_ERROR_CODES) {
|
|
3850
|
+
return {
|
|
3851
|
+
terminal: false,
|
|
3852
|
+
code: RECOVERABLE_SERVER_ERROR_CODES[rawCode],
|
|
3853
|
+
message,
|
|
3854
|
+
retryable: true,
|
|
3855
|
+
frame: obj
|
|
3856
|
+
};
|
|
3857
|
+
}
|
|
3858
|
+
return {
|
|
3859
|
+
terminal: false,
|
|
3860
|
+
code: "unknown",
|
|
3861
|
+
message: rawCode ? `${message} (code=${rawCode})` : message,
|
|
3862
|
+
retryable: true,
|
|
3863
|
+
frame: obj
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3623
3866
|
function readStatus(error) {
|
|
3624
3867
|
if (typeof error !== "object" || error === null) return void 0;
|
|
3625
3868
|
const status = error.statusCode;
|
|
3626
3869
|
return typeof status === "number" ? status : void 0;
|
|
3627
3870
|
}
|
|
3628
|
-
function
|
|
3871
|
+
function safeStringify2(value) {
|
|
3629
3872
|
try {
|
|
3630
3873
|
return JSON.stringify(value);
|
|
3631
3874
|
} catch {
|
|
@@ -3710,6 +3953,450 @@ var FunctionsResource = class extends WorkspaceScopedResource {
|
|
|
3710
3953
|
}
|
|
3711
3954
|
};
|
|
3712
3955
|
|
|
3956
|
+
// src/core/reconnecting-websocket.ts
|
|
3957
|
+
var ReconnectingWebSocketError = class extends Error {
|
|
3958
|
+
reason;
|
|
3959
|
+
closeCode;
|
|
3960
|
+
closeReason;
|
|
3961
|
+
attempts;
|
|
3962
|
+
constructor(message, reason, closeCode, closeReason, attempts) {
|
|
3963
|
+
super(message);
|
|
3964
|
+
this.name = "ReconnectingWebSocketError";
|
|
3965
|
+
this.reason = reason;
|
|
3966
|
+
this.closeCode = closeCode;
|
|
3967
|
+
this.closeReason = closeReason;
|
|
3968
|
+
this.attempts = attempts;
|
|
3969
|
+
}
|
|
3970
|
+
};
|
|
3971
|
+
var TERMINAL_CLOSE_CODES = /* @__PURE__ */ new Set([1008, 4001, 4003, 4100, 4403]);
|
|
3972
|
+
var RATE_LIMITED_CLOSE_CODES = /* @__PURE__ */ new Set([1013, 4029]);
|
|
3973
|
+
var RATE_LIMITED_FLOOR_MS = 5e3;
|
|
3974
|
+
var DEFAULT_INITIAL_DELAY_MS2 = 1e3;
|
|
3975
|
+
var DEFAULT_MAX_DELAY_MS2 = 3e4;
|
|
3976
|
+
var DEFAULT_MAX_RECONNECTS2 = 10;
|
|
3977
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 45e3;
|
|
3978
|
+
function createReconnectingWebSocket(options) {
|
|
3979
|
+
const factory = resolveWebSocketFactory(options.webSocketFactory);
|
|
3980
|
+
const initialDelayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS2;
|
|
3981
|
+
const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS2;
|
|
3982
|
+
const maxReconnects = options.maxReconnects ?? DEFAULT_MAX_RECONNECTS2;
|
|
3983
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
3984
|
+
const localController = new AbortController();
|
|
3985
|
+
if (options.signal) {
|
|
3986
|
+
if (options.signal.aborted) {
|
|
3987
|
+
localController.abort(options.signal.reason);
|
|
3988
|
+
} else {
|
|
3989
|
+
const onAbort = () => localController.abort(options.signal?.reason);
|
|
3990
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
let currentSocket = null;
|
|
3994
|
+
let state = "connecting";
|
|
3995
|
+
let errorReported = false;
|
|
3996
|
+
function setState(next) {
|
|
3997
|
+
if (state === next) return;
|
|
3998
|
+
state = next;
|
|
3999
|
+
try {
|
|
4000
|
+
options.onStateChange?.(next);
|
|
4001
|
+
} catch {
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
function reportError(err) {
|
|
4005
|
+
if (errorReported) return;
|
|
4006
|
+
errorReported = true;
|
|
4007
|
+
setState("terminal");
|
|
4008
|
+
try {
|
|
4009
|
+
options.onError?.(err);
|
|
4010
|
+
} catch {
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
const handle = {
|
|
4014
|
+
get state() {
|
|
4015
|
+
return state;
|
|
4016
|
+
},
|
|
4017
|
+
get done() {
|
|
4018
|
+
return done;
|
|
4019
|
+
},
|
|
4020
|
+
send(data) {
|
|
4021
|
+
if (!currentSocket || currentSocket.readyState !== 1) {
|
|
4022
|
+
throw new Error(`Cannot send on socket in state ${state}`);
|
|
4023
|
+
}
|
|
4024
|
+
currentSocket.send(data);
|
|
4025
|
+
},
|
|
4026
|
+
close(code, reason) {
|
|
4027
|
+
localController.abort(new Error(reason ?? "closed"));
|
|
4028
|
+
try {
|
|
4029
|
+
currentSocket?.close(code, reason);
|
|
4030
|
+
} catch {
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
4034
|
+
const done = runLoop({
|
|
4035
|
+
factory,
|
|
4036
|
+
options,
|
|
4037
|
+
initialDelayMs,
|
|
4038
|
+
maxDelayMs,
|
|
4039
|
+
maxReconnects,
|
|
4040
|
+
idleTimeoutMs,
|
|
4041
|
+
signal: localController.signal,
|
|
4042
|
+
setState,
|
|
4043
|
+
reportError,
|
|
4044
|
+
setSocket: (s) => {
|
|
4045
|
+
currentSocket = s;
|
|
4046
|
+
}
|
|
4047
|
+
});
|
|
4048
|
+
return handle;
|
|
4049
|
+
}
|
|
4050
|
+
async function runLoop(args) {
|
|
4051
|
+
const { options, signal, setState, reportError, setSocket } = args;
|
|
4052
|
+
let attempt = 0;
|
|
4053
|
+
let delayMs = args.initialDelayMs;
|
|
4054
|
+
while (!signal.aborted) {
|
|
4055
|
+
if (attempt > 0) {
|
|
4056
|
+
setState("reconnecting");
|
|
4057
|
+
const sleepMs = jitter2(delayMs);
|
|
4058
|
+
try {
|
|
4059
|
+
options.onReconnect?.({ attempt, delayMs: sleepMs, closeCode: void 0 });
|
|
4060
|
+
} catch {
|
|
4061
|
+
}
|
|
4062
|
+
const slept = await abortableSleep2(sleepMs, signal);
|
|
4063
|
+
if (!slept) break;
|
|
4064
|
+
delayMs = Math.min(delayMs * 2, args.maxDelayMs);
|
|
4065
|
+
}
|
|
4066
|
+
setState(attempt === 0 ? "connecting" : "connecting");
|
|
4067
|
+
let outcome;
|
|
4068
|
+
try {
|
|
4069
|
+
outcome = await runOneConnection2(args);
|
|
4070
|
+
} catch (err) {
|
|
4071
|
+
reportError(
|
|
4072
|
+
new ReconnectingWebSocketError(
|
|
4073
|
+
err instanceof Error ? err.message : "Failed to open WebSocket",
|
|
4074
|
+
"open_failed",
|
|
4075
|
+
void 0,
|
|
4076
|
+
void 0,
|
|
4077
|
+
attempt
|
|
4078
|
+
)
|
|
4079
|
+
);
|
|
4080
|
+
return;
|
|
4081
|
+
} finally {
|
|
4082
|
+
setSocket(null);
|
|
4083
|
+
}
|
|
4084
|
+
if (outcome.closeCode !== void 0 && TERMINAL_CLOSE_CODES.has(outcome.closeCode)) {
|
|
4085
|
+
reportError(
|
|
4086
|
+
new ReconnectingWebSocketError(
|
|
4087
|
+
`Server closed with terminal code ${outcome.closeCode}: ${outcome.closeReason ?? ""}`,
|
|
4088
|
+
outcome.closeCode === 4403 ? "auth" : "client_error",
|
|
4089
|
+
outcome.closeCode,
|
|
4090
|
+
outcome.closeReason,
|
|
4091
|
+
attempt
|
|
4092
|
+
)
|
|
4093
|
+
);
|
|
4094
|
+
return;
|
|
4095
|
+
}
|
|
4096
|
+
if (outcome.aborted || signal.aborted) {
|
|
4097
|
+
setState("closed");
|
|
4098
|
+
return;
|
|
4099
|
+
}
|
|
4100
|
+
if (attempt >= args.maxReconnects) {
|
|
4101
|
+
reportError(
|
|
4102
|
+
new ReconnectingWebSocketError(
|
|
4103
|
+
`Reconnect budget exhausted (${args.maxReconnects} attempts)`,
|
|
4104
|
+
"reconnect_budget_exhausted",
|
|
4105
|
+
outcome.closeCode,
|
|
4106
|
+
outcome.closeReason,
|
|
4107
|
+
attempt
|
|
4108
|
+
)
|
|
4109
|
+
);
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
if (outcome.closeCode !== void 0 && RATE_LIMITED_CLOSE_CODES.has(outcome.closeCode)) {
|
|
4113
|
+
delayMs = Math.max(delayMs, RATE_LIMITED_FLOOR_MS);
|
|
4114
|
+
}
|
|
4115
|
+
attempt += 1;
|
|
4116
|
+
}
|
|
4117
|
+
setState("closed");
|
|
4118
|
+
}
|
|
4119
|
+
async function runOneConnection2(args) {
|
|
4120
|
+
const { options, factory, signal, setState, setSocket, idleTimeoutMs } = args;
|
|
4121
|
+
let socket;
|
|
4122
|
+
try {
|
|
4123
|
+
socket = factory(options.url, options.protocols);
|
|
4124
|
+
} catch (err) {
|
|
4125
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
4126
|
+
}
|
|
4127
|
+
setSocket(socket);
|
|
4128
|
+
return new Promise((resolve) => {
|
|
4129
|
+
let watchdogTimer = null;
|
|
4130
|
+
let resolved = false;
|
|
4131
|
+
function clearWatchdog() {
|
|
4132
|
+
if (watchdogTimer !== null) {
|
|
4133
|
+
clearTimeout(watchdogTimer);
|
|
4134
|
+
watchdogTimer = null;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
function armWatchdog() {
|
|
4138
|
+
if (idleTimeoutMs <= 0) return;
|
|
4139
|
+
clearWatchdog();
|
|
4140
|
+
watchdogTimer = setTimeout(() => {
|
|
4141
|
+
if (resolved) return;
|
|
4142
|
+
try {
|
|
4143
|
+
socket.close(4001, "idle timeout");
|
|
4144
|
+
} catch {
|
|
4145
|
+
}
|
|
4146
|
+
finalize({
|
|
4147
|
+
closeCode: 4001,
|
|
4148
|
+
closeReason: "idle timeout",
|
|
4149
|
+
watchdogTriggered: true,
|
|
4150
|
+
aborted: false
|
|
4151
|
+
});
|
|
4152
|
+
}, idleTimeoutMs);
|
|
4153
|
+
}
|
|
4154
|
+
function finalize(outcome) {
|
|
4155
|
+
if (resolved) return;
|
|
4156
|
+
resolved = true;
|
|
4157
|
+
clearWatchdog();
|
|
4158
|
+
signal.removeEventListener("abort", onAbort);
|
|
4159
|
+
try {
|
|
4160
|
+
socket.removeEventListener("open", onOpen);
|
|
4161
|
+
socket.removeEventListener("message", onMessage);
|
|
4162
|
+
socket.removeEventListener("close", onClose);
|
|
4163
|
+
socket.removeEventListener("error", onSocketError);
|
|
4164
|
+
} catch {
|
|
4165
|
+
}
|
|
4166
|
+
resolve(outcome);
|
|
4167
|
+
}
|
|
4168
|
+
function onOpen() {
|
|
4169
|
+
setState("open");
|
|
4170
|
+
armWatchdog();
|
|
4171
|
+
}
|
|
4172
|
+
function onMessage(ev) {
|
|
4173
|
+
armWatchdog();
|
|
4174
|
+
try {
|
|
4175
|
+
options.onMessage(ev);
|
|
4176
|
+
} catch {
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
function onClose(ev) {
|
|
4180
|
+
setState("closed");
|
|
4181
|
+
finalize({
|
|
4182
|
+
closeCode: ev.code,
|
|
4183
|
+
closeReason: ev.reason,
|
|
4184
|
+
watchdogTriggered: false,
|
|
4185
|
+
aborted: false
|
|
4186
|
+
});
|
|
4187
|
+
}
|
|
4188
|
+
function onSocketError() {
|
|
4189
|
+
}
|
|
4190
|
+
function onAbort() {
|
|
4191
|
+
try {
|
|
4192
|
+
socket.close(1e3, "client aborted");
|
|
4193
|
+
} catch {
|
|
4194
|
+
}
|
|
4195
|
+
finalize({
|
|
4196
|
+
closeCode: void 0,
|
|
4197
|
+
closeReason: void 0,
|
|
4198
|
+
watchdogTriggered: false,
|
|
4199
|
+
aborted: true
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
if (signal.aborted) {
|
|
4203
|
+
onAbort();
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
socket.addEventListener("open", onOpen);
|
|
4207
|
+
socket.addEventListener("message", onMessage);
|
|
4208
|
+
socket.addEventListener(
|
|
4209
|
+
"close",
|
|
4210
|
+
onClose
|
|
4211
|
+
);
|
|
4212
|
+
socket.addEventListener("error", onSocketError);
|
|
4213
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4214
|
+
});
|
|
4215
|
+
}
|
|
4216
|
+
function resolveWebSocketFactory(factory) {
|
|
4217
|
+
if (factory) return factory;
|
|
4218
|
+
const globalWs = globalThis.WebSocket;
|
|
4219
|
+
if (!globalWs) {
|
|
4220
|
+
return () => {
|
|
4221
|
+
throw new Error(
|
|
4222
|
+
"No global WebSocket available; pass webSocketFactory to createReconnectingWebSocket"
|
|
4223
|
+
);
|
|
4224
|
+
};
|
|
4225
|
+
}
|
|
4226
|
+
return (url, protocols) => new globalWs(url, protocols);
|
|
4227
|
+
}
|
|
4228
|
+
function jitter2(ms) {
|
|
4229
|
+
return Math.floor(Math.random() * Math.max(1, ms));
|
|
4230
|
+
}
|
|
4231
|
+
async function abortableSleep2(ms, signal) {
|
|
4232
|
+
if (signal.aborted) return false;
|
|
4233
|
+
return new Promise((resolve) => {
|
|
4234
|
+
const timer = setTimeout(() => {
|
|
4235
|
+
signal.removeEventListener("abort", onAbort);
|
|
4236
|
+
resolve(true);
|
|
4237
|
+
}, ms);
|
|
4238
|
+
const onAbort = () => {
|
|
4239
|
+
clearTimeout(timer);
|
|
4240
|
+
signal.removeEventListener("abort", onAbort);
|
|
4241
|
+
resolve(false);
|
|
4242
|
+
};
|
|
4243
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4244
|
+
});
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
// src/resources/observers.ts
|
|
4248
|
+
var WEB_SOCKET_PROTOCOL_TOKEN_RE2 = /^[!#$%&'*+\-.^_`|~A-Za-z0-9]+$/;
|
|
4249
|
+
var MAX_AUTH_TOKEN_CHARS2 = 4096;
|
|
4250
|
+
function observerAuthProtocols(token) {
|
|
4251
|
+
if (!token) {
|
|
4252
|
+
throw new ConfigurationError("observerAuthProtocols requires a non-empty token");
|
|
4253
|
+
}
|
|
4254
|
+
if (token.length > MAX_AUTH_TOKEN_CHARS2) {
|
|
4255
|
+
throw new ConfigurationError(
|
|
4256
|
+
`observer token exceeds the ${MAX_AUTH_TOKEN_CHARS2}-character WebSocket subprotocol limit`
|
|
4257
|
+
);
|
|
4258
|
+
}
|
|
4259
|
+
if (!WEB_SOCKET_PROTOCOL_TOKEN_RE2.test(token)) {
|
|
4260
|
+
throw new ConfigurationError(
|
|
4261
|
+
"observer token contains characters browsers reject in WebSocket subprotocols"
|
|
4262
|
+
);
|
|
4263
|
+
}
|
|
4264
|
+
return ["auth", token];
|
|
4265
|
+
}
|
|
4266
|
+
var ObserversResource = class extends WorkspaceScopedResource {
|
|
4267
|
+
agentBaseUrl;
|
|
4268
|
+
constructor(client, workspaceId2, agentBaseUrl) {
|
|
4269
|
+
super(client, workspaceId2);
|
|
4270
|
+
this.agentBaseUrl = agentBaseUrl;
|
|
4271
|
+
}
|
|
4272
|
+
/**
|
|
4273
|
+
* Subscribe to the live observer stream for a call.
|
|
4274
|
+
*
|
|
4275
|
+
* Returns a {@link ReconnectingWebSocketHandle} that resolves
|
|
4276
|
+
* ``handle.done`` when the stream terminates (consumer-aborted, terminal
|
|
4277
|
+
* close code, or reconnect budget exhausted). Errors are surfaced through
|
|
4278
|
+
* ``onError``; the promise never rejects.
|
|
4279
|
+
*/
|
|
4280
|
+
subscribe(options) {
|
|
4281
|
+
const url = buildObserverUrl({
|
|
4282
|
+
baseUrl: this.agentBaseUrl ?? this.platformBaseUrl,
|
|
4283
|
+
workspaceId: this.workspaceId,
|
|
4284
|
+
callSid: options.callSid,
|
|
4285
|
+
observerUrl: options.observerUrl
|
|
4286
|
+
});
|
|
4287
|
+
const protocols = observerAuthProtocols(options.token);
|
|
4288
|
+
return createReconnectingWebSocket({
|
|
4289
|
+
url,
|
|
4290
|
+
protocols: [...protocols],
|
|
4291
|
+
onMessage: (ev) => {
|
|
4292
|
+
const parsed = parseObserverFrame(ev.data);
|
|
4293
|
+
if (parsed) {
|
|
4294
|
+
try {
|
|
4295
|
+
options.onEvent(parsed);
|
|
4296
|
+
} catch {
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
},
|
|
4300
|
+
onStateChange: options.onStateChange,
|
|
4301
|
+
onReconnect: options.onReconnect,
|
|
4302
|
+
onError: options.onError,
|
|
4303
|
+
signal: options.signal,
|
|
4304
|
+
idleTimeoutMs: options.idleTimeoutMs ?? 6e4,
|
|
4305
|
+
initialDelayMs: options.initialDelayMs ?? 1e3,
|
|
4306
|
+
maxDelayMs: options.maxDelayMs ?? 3e4,
|
|
4307
|
+
maxReconnects: options.maxReconnects ?? 10,
|
|
4308
|
+
webSocketFactory: options.webSocketFactory
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
};
|
|
4312
|
+
var CALL_SID_RE = /^CA[a-zA-Z0-9]{32}$/;
|
|
4313
|
+
function buildObserverUrl(args) {
|
|
4314
|
+
if (!args.workspaceId) {
|
|
4315
|
+
throw new ConfigurationError("workspaceId is required to build the observer URL");
|
|
4316
|
+
}
|
|
4317
|
+
if (!args.callSid) {
|
|
4318
|
+
throw new ConfigurationError("callSid is required to subscribe to the observer stream");
|
|
4319
|
+
}
|
|
4320
|
+
if (args.observerUrl) {
|
|
4321
|
+
return parseOverride(args.observerUrl).toString();
|
|
4322
|
+
}
|
|
4323
|
+
if (!CALL_SID_RE.test(args.callSid)) {
|
|
4324
|
+
throw new ConfigurationError(
|
|
4325
|
+
`callSid does not match Twilio CA SID format (CA + 32 hex chars): ${args.callSid}`
|
|
4326
|
+
);
|
|
4327
|
+
}
|
|
4328
|
+
return deriveFromBase(args.baseUrl, args.workspaceId, args.callSid).toString();
|
|
4329
|
+
}
|
|
4330
|
+
function parseOverride(observerUrl) {
|
|
4331
|
+
let url;
|
|
4332
|
+
try {
|
|
4333
|
+
url = new URL(observerUrl);
|
|
4334
|
+
} catch (cause) {
|
|
4335
|
+
throw new ConfigurationError(
|
|
4336
|
+
`observerUrl must be an absolute URL: ${String(cause)}`
|
|
4337
|
+
);
|
|
4338
|
+
}
|
|
4339
|
+
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
|
4340
|
+
throw new ConfigurationError("observerUrl overrides must use ws: or wss: URLs");
|
|
4341
|
+
}
|
|
4342
|
+
if (url.search || url.hash) {
|
|
4343
|
+
throw new ConfigurationError(
|
|
4344
|
+
"observerUrl overrides must not include query parameters or fragments"
|
|
4345
|
+
);
|
|
4346
|
+
}
|
|
4347
|
+
return url;
|
|
4348
|
+
}
|
|
4349
|
+
function deriveFromBase(baseUrl, workspaceId2, callSid) {
|
|
4350
|
+
let parsed;
|
|
4351
|
+
try {
|
|
4352
|
+
parsed = new URL(baseUrl);
|
|
4353
|
+
} catch (cause) {
|
|
4354
|
+
throw new ConfigurationError(
|
|
4355
|
+
`observerUrl cannot be derived from baseUrl: ${String(cause)}`
|
|
4356
|
+
);
|
|
4357
|
+
}
|
|
4358
|
+
let scheme;
|
|
4359
|
+
if (parsed.protocol === "https:" || parsed.protocol === "wss:") scheme = "wss:";
|
|
4360
|
+
else if (parsed.protocol === "http:" || parsed.protocol === "ws:") scheme = "ws:";
|
|
4361
|
+
else {
|
|
4362
|
+
throw new ConfigurationError(
|
|
4363
|
+
`observerUrl can only be derived from an http, https, ws, or wss baseUrl: ${baseUrl}`
|
|
4364
|
+
);
|
|
4365
|
+
}
|
|
4366
|
+
if (parsed.pathname !== "/" && parsed.pathname !== "") {
|
|
4367
|
+
throw new ConfigurationError(
|
|
4368
|
+
"observerUrl can only be derived from an origin-only baseUrl; pass observerUrl explicitly when using path-prefixed gateways"
|
|
4369
|
+
);
|
|
4370
|
+
}
|
|
4371
|
+
const out = new URL(`${scheme}//${parsed.host}/v1/${encodeURIComponent(workspaceId2)}/observers/${encodeURIComponent(callSid)}/ws`);
|
|
4372
|
+
return out;
|
|
4373
|
+
}
|
|
4374
|
+
function parseObserverFrame(data) {
|
|
4375
|
+
let text;
|
|
4376
|
+
if (typeof data === "string") {
|
|
4377
|
+
text = data;
|
|
4378
|
+
} else if (data instanceof ArrayBuffer) {
|
|
4379
|
+
text = new TextDecoder().decode(data);
|
|
4380
|
+
} else if (ArrayBuffer.isView(data)) {
|
|
4381
|
+
const view = data;
|
|
4382
|
+
text = new TextDecoder().decode(
|
|
4383
|
+
new Uint8Array(view.buffer, view.byteOffset, view.byteLength)
|
|
4384
|
+
);
|
|
4385
|
+
} else {
|
|
4386
|
+
return null;
|
|
4387
|
+
}
|
|
4388
|
+
let payload;
|
|
4389
|
+
try {
|
|
4390
|
+
payload = JSON.parse(text);
|
|
4391
|
+
} catch {
|
|
4392
|
+
return null;
|
|
4393
|
+
}
|
|
4394
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return null;
|
|
4395
|
+
const obj = payload;
|
|
4396
|
+
if (typeof obj["type"] !== "string") return null;
|
|
4397
|
+
return obj;
|
|
4398
|
+
}
|
|
4399
|
+
|
|
3713
4400
|
// src/core/branded-types.ts
|
|
3714
4401
|
var workspaceId = (id) => id;
|
|
3715
4402
|
var apiKeyId = (id) => id;
|
|
@@ -4373,6 +5060,12 @@ var AmigoClient = class _AmigoClient {
|
|
|
4373
5060
|
compliance;
|
|
4374
5061
|
events;
|
|
4375
5062
|
functions;
|
|
5063
|
+
/**
|
|
5064
|
+
* Voice-call observer real-time stream. Subscribe with
|
|
5065
|
+
* ``client.observers.subscribe({ callSid, token, onEvent })``. See
|
|
5066
|
+
* {@link ObserversResource}.
|
|
5067
|
+
*/
|
|
5068
|
+
observers;
|
|
4376
5069
|
/** @internal — exposed for path-level type inference in GET/POST/PUT/etc. */
|
|
4377
5070
|
api;
|
|
4378
5071
|
constructor(config) {
|
|
@@ -4428,6 +5121,10 @@ var AmigoClient = class _AmigoClient {
|
|
|
4428
5121
|
allowEmptyBody: true
|
|
4429
5122
|
});
|
|
4430
5123
|
}
|
|
5124
|
+
defineRoute(method, path) {
|
|
5125
|
+
const dispatcher = this[method];
|
|
5126
|
+
return (init) => dispatcher.call(this, path, init);
|
|
5127
|
+
}
|
|
4431
5128
|
static fromPlatformClient(client, workspaceId2, baseUrl, agentBaseUrl) {
|
|
4432
5129
|
const instance = Object.create(_AmigoClient.prototype);
|
|
4433
5130
|
_AmigoClient.hydrate(instance, client, workspaceId2, baseUrl, agentBaseUrl);
|
|
@@ -4469,6 +5166,7 @@ var AmigoClient = class _AmigoClient {
|
|
|
4469
5166
|
mutable.compliance = new ComplianceResource(client, workspaceId2);
|
|
4470
5167
|
mutable.events = new EventsResource(client, workspaceId2);
|
|
4471
5168
|
mutable.functions = new FunctionsResource(client, workspaceId2);
|
|
5169
|
+
mutable.observers = new ObserversResource(client, workspaceId2, agentBaseUrl);
|
|
4472
5170
|
}
|
|
4473
5171
|
async resolveApiRequest(path, method, init) {
|
|
4474
5172
|
const { baseClient, options } = resolveScopedPlatformClient(this.api);
|