@amigo-ai/platform-sdk 0.23.0 → 0.25.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.
Files changed (42) hide show
  1. package/api.md +8 -2
  2. package/dist/core/errors.js +155 -9
  3. package/dist/core/errors.js.map +1 -1
  4. package/dist/core/reconnecting-websocket.js +371 -0
  5. package/dist/core/reconnecting-websocket.js.map +1 -0
  6. package/dist/index.cjs +1021 -17
  7. package/dist/index.cjs.map +4 -4
  8. package/dist/index.js +22 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +1021 -17
  11. package/dist/index.mjs.map +4 -4
  12. package/dist/resources/events.js +588 -0
  13. package/dist/resources/events.js.map +1 -0
  14. package/dist/resources/integrations.js +25 -0
  15. package/dist/resources/integrations.js.map +1 -1
  16. package/dist/resources/observers.js +238 -0
  17. package/dist/resources/observers.js.map +1 -0
  18. package/dist/resources/workspaces.js +4 -8
  19. package/dist/resources/workspaces.js.map +1 -1
  20. package/dist/types/core/errors.d.ts +93 -1
  21. package/dist/types/core/errors.d.ts.map +1 -1
  22. package/dist/types/core/reconnecting-websocket.d.ts +156 -0
  23. package/dist/types/core/reconnecting-websocket.d.ts.map +1 -0
  24. package/dist/types/generated/api.d.ts +628 -104
  25. package/dist/types/generated/api.d.ts.map +1 -1
  26. package/dist/types/index.d.cts +45 -2
  27. package/dist/types/index.d.cts.map +1 -1
  28. package/dist/types/index.d.ts +45 -2
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/dist/types/resources/events.d.ts +193 -0
  31. package/dist/types/resources/events.d.ts.map +1 -0
  32. package/dist/types/resources/functions.d.ts.map +1 -1
  33. package/dist/types/resources/integrations.d.ts +33 -0
  34. package/dist/types/resources/integrations.d.ts.map +1 -1
  35. package/dist/types/resources/metrics.d.ts.map +1 -1
  36. package/dist/types/resources/observers.d.ts +148 -0
  37. package/dist/types/resources/observers.d.ts.map +1 -0
  38. package/dist/types/resources/operators.d.ts.map +1 -1
  39. package/dist/types/resources/settings.d.ts.map +1 -1
  40. package/dist/types/resources/workspaces.d.ts +4 -15
  41. package/dist/types/resources/workspaces.d.ts.map +1 -1
  42. 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 createApiError(response) {
232
- let body = {};
262
+ async function readErrorBody(response) {
263
+ let rawBody = "";
264
+ try {
265
+ rawBody = await response.text();
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
+ };
278
+ }
233
279
  try {
234
- const rawBody = await response.text();
235
- body = JSON.parse(rawBody);
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
+ };
236
291
  } catch {
292
+ return {
293
+ body: {
294
+ detail: response.statusText || `HTTP ${response.status}`,
295
+ raw_body: truncatedRaw
296
+ },
297
+ rawBody: truncatedRaw
298
+ };
237
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: body.error_code,
241
- requestId: body.request_id ?? response.headers.get("x-request-id") ?? void 0,
242
- detail: body.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 = body.message ?? body.detail ?? response.statusText ?? `HTTP ${response.status}`;
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);
@@ -882,14 +995,6 @@ function resolveScopedPlatformClient(client) {
882
995
 
883
996
  // src/resources/workspaces.ts
884
997
  var WorkspacesResource = class extends WorkspaceScopedResource {
885
- /** Create a new workspace (unauthenticated — no owner membership created) */
886
- async create(body) {
887
- return extractData(
888
- await this.client.POST("/v1/workspaces", {
889
- body
890
- })
891
- );
892
- }
893
998
  /** Create a workspace for the authenticated user and attach owner access */
894
999
  async createSelfService(body) {
895
1000
  return extractData(
@@ -2463,6 +2568,36 @@ var IntegrationsResource = class extends WorkspaceScopedResource {
2463
2568
  )
2464
2569
  );
2465
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
+ }
2466
2601
  /** Check health of all integrations in the workspace */
2467
2602
  async getHealthCheck() {
2468
2603
  return extractData(
@@ -3352,6 +3487,418 @@ var ComplianceResource = class extends WorkspaceScopedResource {
3352
3487
  }
3353
3488
  };
3354
3489
 
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
+ }
3507
+ var DEFAULT_INITIAL_DELAY_MS = 3e3;
3508
+ var DEFAULT_MAX_DELAY_MS = 3e4;
3509
+ var DEFAULT_MAX_RECONNECTS = 10;
3510
+ var MAX_FRAME_BYTES = 1048576;
3511
+ var EventsResource = class extends WorkspaceScopedResource {
3512
+ constructor(client, workspaceId2) {
3513
+ super(client, workspaceId2);
3514
+ }
3515
+ /**
3516
+ * Subscribe to the workspace event stream.
3517
+ *
3518
+ * Establishes an SSE connection to ``/v1/{workspace_id}/events/stream``
3519
+ * and invokes ``onEvent`` once per typed {@link WorkspaceSSEEvent}.
3520
+ * Unrecoverable failures (auth errors, exhausted reconnect budget,
3521
+ * caller abort) surface through ``onError``.
3522
+ *
3523
+ * Reconnection is automatic: on a network drop or 5xx, the helper
3524
+ * backs off (initial delay derived from the ``retry:`` directive,
3525
+ * default 3s, doubling with full jitter up to ``maxDelayMs``) and
3526
+ * resumes with the most recently seen ``Last-Event-ID``. The platform
3527
+ * buffers 5 minutes of events for gapless replay.
3528
+ *
3529
+ * @returns a {@link SubscriptionHandle} for cleanup. Aborting the
3530
+ * caller's ``signal`` is equivalent to calling ``unsubscribe()``.
3531
+ */
3532
+ subscribeToWorkspace(options) {
3533
+ const localController = new AbortController();
3534
+ const cleanups = [];
3535
+ if (options.signal) {
3536
+ if (options.signal.aborted) {
3537
+ localController.abort(options.signal.reason);
3538
+ } else {
3539
+ const onAbort = () => localController.abort(options.signal?.reason);
3540
+ options.signal.addEventListener("abort", onAbort, { once: true });
3541
+ cleanups.push(() => options.signal?.removeEventListener("abort", onAbort));
3542
+ }
3543
+ }
3544
+ const done = runSubscription(
3545
+ this.client,
3546
+ this.workspaceId,
3547
+ options,
3548
+ localController.signal
3549
+ ).finally(() => {
3550
+ for (const cleanup of cleanups) cleanup();
3551
+ });
3552
+ return {
3553
+ done,
3554
+ unsubscribe: () => localController.abort()
3555
+ };
3556
+ }
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
+ };
3565
+ async function runSubscription(client, workspaceId2, options, signal) {
3566
+ let lastEventId = options.lastEventId;
3567
+ let attempt = 0;
3568
+ let delayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
3569
+ const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
3570
+ const maxReconnects = options.maxReconnects ?? DEFAULT_MAX_RECONNECTS;
3571
+ let errorReported = false;
3572
+ const reportError = (error) => {
3573
+ if (errorReported) return;
3574
+ errorReported = true;
3575
+ try {
3576
+ options.onError?.(error);
3577
+ } catch {
3578
+ }
3579
+ };
3580
+ while (!signal.aborted) {
3581
+ if (attempt > 0) {
3582
+ try {
3583
+ options.onReconnect?.(attempt);
3584
+ } catch {
3585
+ }
3586
+ }
3587
+ let outcome;
3588
+ try {
3589
+ outcome = await runOneConnection({
3590
+ client,
3591
+ workspaceId: workspaceId2,
3592
+ lastEventId,
3593
+ signal,
3594
+ onEvent: options.onEvent,
3595
+ onIdAdvance: (id) => {
3596
+ lastEventId = id;
3597
+ },
3598
+ onRetryDirective: (ms) => {
3599
+ delayMs = clampDelay(ms, options.initialDelayMs, maxDelayMs);
3600
+ }
3601
+ });
3602
+ } catch (err) {
3603
+ reportError(err instanceof Error ? err : new Error(String(err)));
3604
+ return;
3605
+ }
3606
+ if (signal.aborted || outcome.kind === "aborted") {
3607
+ return;
3608
+ }
3609
+ if (outcome.kind === "auth-error") {
3610
+ reportError(outcome.error);
3611
+ return;
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
+ }
3624
+ if (attempt >= maxReconnects) {
3625
+ reportError(
3626
+ new WorkspaceEventStreamError(
3627
+ `SSE subscription exhausted reconnect budget (${maxReconnects}): ${outcome.reason}`,
3628
+ "transport_exhausted",
3629
+ true
3630
+ )
3631
+ );
3632
+ return;
3633
+ }
3634
+ attempt += 1;
3635
+ const sleepMs = jitter(delayMs);
3636
+ delayMs = Math.min(delayMs * 2, maxDelayMs);
3637
+ const slept = await abortableSleep(sleepMs, signal);
3638
+ if (!slept) return;
3639
+ }
3640
+ }
3641
+ async function runOneConnection(args) {
3642
+ const headers = { Accept: "text/event-stream" };
3643
+ if (args.lastEventId !== void 0) {
3644
+ headers["Last-Event-ID"] = args.lastEventId;
3645
+ }
3646
+ let result;
3647
+ try {
3648
+ result = await args.client.GET("/v1/{workspace_id}/events/stream", {
3649
+ params: { path: { workspace_id: args.workspaceId } },
3650
+ headers,
3651
+ parseAs: "stream",
3652
+ signal: args.signal
3653
+ });
3654
+ } catch (err) {
3655
+ if (args.signal.aborted) return { kind: "aborted" };
3656
+ const error = err instanceof Error ? err : new Error(String(err));
3657
+ const status = readStatus(error);
3658
+ if (status === 401 || status === 403) {
3659
+ return { kind: "auth-error", error };
3660
+ }
3661
+ return { kind: "transport-error", reason: error.message };
3662
+ }
3663
+ if (result.error !== void 0) {
3664
+ return { kind: "transport-error", reason: `API error: ${safeStringify2(result.error)}` };
3665
+ }
3666
+ const body = result.data;
3667
+ if (!(body instanceof ReadableStream)) {
3668
+ return { kind: "transport-error", reason: "Expected ReadableStream body for SSE" };
3669
+ }
3670
+ try {
3671
+ for await (const frame of parseSSEFrames2(body, args.signal)) {
3672
+ if (args.signal.aborted) return { kind: "aborted" };
3673
+ if (frame.retry !== void 0) {
3674
+ args.onRetryDirective(frame.retry);
3675
+ }
3676
+ if (frame.id !== void 0) {
3677
+ args.onIdAdvance(frame.id);
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
+ }
3692
+ if (frame.event && frame.data !== void 0) {
3693
+ const event = parseWorkspaceFrame(frame.event, frame.data);
3694
+ if (event) {
3695
+ try {
3696
+ args.onEvent(event);
3697
+ } catch {
3698
+ }
3699
+ }
3700
+ }
3701
+ }
3702
+ } catch (err) {
3703
+ if (args.signal.aborted) return { kind: "aborted" };
3704
+ const reason = err instanceof Error ? err.message : String(err);
3705
+ return { kind: "transport-error", reason };
3706
+ }
3707
+ if (args.signal.aborted) return { kind: "aborted" };
3708
+ return { kind: "transport-error", reason: "Stream closed by server" };
3709
+ }
3710
+ async function* parseSSEFrames2(stream, signal) {
3711
+ const reader = stream.getReader();
3712
+ const decoder = new TextDecoder();
3713
+ let buffer = "";
3714
+ let cancelled = false;
3715
+ const onAbort = () => {
3716
+ if (cancelled) return;
3717
+ cancelled = true;
3718
+ void reader.cancel().catch(() => {
3719
+ });
3720
+ };
3721
+ let removeAbortHandler;
3722
+ if (signal.aborted) {
3723
+ onAbort();
3724
+ } else {
3725
+ signal.addEventListener("abort", onAbort, { once: true });
3726
+ removeAbortHandler = () => signal.removeEventListener("abort", onAbort);
3727
+ }
3728
+ function* drain(text) {
3729
+ buffer += text;
3730
+ if (buffer.length > MAX_FRAME_BYTES) {
3731
+ throw new Error(`SSE frame buffer exceeded ${MAX_FRAME_BYTES} bytes without terminator`);
3732
+ }
3733
+ while (true) {
3734
+ const idx = findFrameTerminator2(buffer);
3735
+ if (idx === null) break;
3736
+ const block = buffer.slice(0, idx.terminatorStart);
3737
+ buffer = buffer.slice(idx.terminatorEnd);
3738
+ const frame = parseSSEBlock2(block);
3739
+ if (frame) yield frame;
3740
+ }
3741
+ }
3742
+ try {
3743
+ while (true) {
3744
+ const { done, value } = await reader.read();
3745
+ if (done) break;
3746
+ yield* drain(decoder.decode(value, { stream: true }));
3747
+ }
3748
+ yield* drain(decoder.decode());
3749
+ if (buffer.trim().length > 0) {
3750
+ const frame = parseSSEBlock2(buffer);
3751
+ if (frame) yield frame;
3752
+ buffer = "";
3753
+ }
3754
+ } finally {
3755
+ removeAbortHandler?.();
3756
+ try {
3757
+ reader.releaseLock();
3758
+ } catch {
3759
+ }
3760
+ }
3761
+ }
3762
+ function findFrameTerminator2(s) {
3763
+ const lf = s.indexOf("\n\n");
3764
+ const crlf = s.indexOf("\r\n\r\n");
3765
+ if (lf < 0 && crlf < 0) return null;
3766
+ if (lf < 0) return { terminatorStart: crlf, terminatorEnd: crlf + 4 };
3767
+ if (crlf < 0) return { terminatorStart: lf, terminatorEnd: lf + 2 };
3768
+ return lf < crlf ? { terminatorStart: lf, terminatorEnd: lf + 2 } : { terminatorStart: crlf, terminatorEnd: crlf + 4 };
3769
+ }
3770
+ function parseSSEBlock2(block) {
3771
+ let event = "";
3772
+ let id;
3773
+ let retry;
3774
+ const dataLines = [];
3775
+ for (const line of block.split(/\r?\n/)) {
3776
+ if (line === "" || line.startsWith(":")) continue;
3777
+ const colon = line.indexOf(":");
3778
+ const field = colon < 0 ? line : line.slice(0, colon);
3779
+ let value = colon < 0 ? "" : line.slice(colon + 1);
3780
+ if (value.startsWith(" ")) value = value.slice(1);
3781
+ if (field === "event") {
3782
+ event = value;
3783
+ } else if (field === "data") {
3784
+ dataLines.push(value);
3785
+ } else if (field === "id") {
3786
+ id = value;
3787
+ } else if (field === "retry") {
3788
+ const parsed = Number.parseInt(value, 10);
3789
+ if (Number.isFinite(parsed) && parsed >= 0) retry = parsed;
3790
+ }
3791
+ }
3792
+ if (!event && dataLines.length === 0 && id === void 0 && retry === void 0) {
3793
+ return null;
3794
+ }
3795
+ return {
3796
+ event,
3797
+ data: dataLines.length > 0 ? dataLines.join("\n") : void 0,
3798
+ id,
3799
+ retry
3800
+ };
3801
+ }
3802
+ function parseWorkspaceFrame(eventName, dataJson) {
3803
+ let payload;
3804
+ try {
3805
+ payload = JSON.parse(dataJson);
3806
+ } catch {
3807
+ return null;
3808
+ }
3809
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return null;
3810
+ return {
3811
+ ...payload,
3812
+ event_type: eventName
3813
+ };
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
+ }
3866
+ function readStatus(error) {
3867
+ if (typeof error !== "object" || error === null) return void 0;
3868
+ const status = error.statusCode;
3869
+ return typeof status === "number" ? status : void 0;
3870
+ }
3871
+ function safeStringify2(value) {
3872
+ try {
3873
+ return JSON.stringify(value);
3874
+ } catch {
3875
+ return String(value);
3876
+ }
3877
+ }
3878
+ function jitter(ms) {
3879
+ return Math.floor(Math.random() * Math.max(1, ms));
3880
+ }
3881
+ function clampDelay(ms, floor, ceiling) {
3882
+ const lo = floor ?? DEFAULT_INITIAL_DELAY_MS;
3883
+ if (!Number.isFinite(ms) || ms <= 0) return lo;
3884
+ return Math.min(Math.max(ms, lo), ceiling);
3885
+ }
3886
+ async function abortableSleep(ms, signal) {
3887
+ if (signal.aborted) return false;
3888
+ return new Promise((resolve) => {
3889
+ const timer = setTimeout(() => {
3890
+ signal.removeEventListener("abort", onAbort);
3891
+ resolve(true);
3892
+ }, ms);
3893
+ const onAbort = () => {
3894
+ clearTimeout(timer);
3895
+ signal.removeEventListener("abort", onAbort);
3896
+ resolve(false);
3897
+ };
3898
+ signal.addEventListener("abort", onAbort, { once: true });
3899
+ });
3900
+ }
3901
+
3355
3902
  // src/resources/functions.ts
3356
3903
  var FunctionsResource = class extends WorkspaceScopedResource {
3357
3904
  async list() {
@@ -3406,6 +3953,450 @@ var FunctionsResource = class extends WorkspaceScopedResource {
3406
3953
  }
3407
3954
  };
3408
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
+
3409
4400
  // src/core/branded-types.ts
3410
4401
  var workspaceId = (id) => id;
3411
4402
  var apiKeyId = (id) => id;
@@ -4067,7 +5058,14 @@ var AmigoClient = class _AmigoClient {
4067
5058
  webhookDestinations;
4068
5059
  safety;
4069
5060
  compliance;
5061
+ events;
4070
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;
4071
5069
  /** @internal — exposed for path-level type inference in GET/POST/PUT/etc. */
4072
5070
  api;
4073
5071
  constructor(config) {
@@ -4123,6 +5121,10 @@ var AmigoClient = class _AmigoClient {
4123
5121
  allowEmptyBody: true
4124
5122
  });
4125
5123
  }
5124
+ defineRoute(method, path) {
5125
+ const dispatcher = this[method];
5126
+ return (init) => dispatcher.call(this, path, init);
5127
+ }
4126
5128
  static fromPlatformClient(client, workspaceId2, baseUrl, agentBaseUrl) {
4127
5129
  const instance = Object.create(_AmigoClient.prototype);
4128
5130
  _AmigoClient.hydrate(instance, client, workspaceId2, baseUrl, agentBaseUrl);
@@ -4162,7 +5164,9 @@ var AmigoClient = class _AmigoClient {
4162
5164
  mutable.webhookDestinations = new WebhookDestinationsResource(client, workspaceId2);
4163
5165
  mutable.safety = new SafetyResource(client, workspaceId2);
4164
5166
  mutable.compliance = new ComplianceResource(client, workspaceId2);
5167
+ mutable.events = new EventsResource(client, workspaceId2);
4165
5168
  mutable.functions = new FunctionsResource(client, workspaceId2);
5169
+ mutable.observers = new ObserversResource(client, workspaceId2, agentBaseUrl);
4166
5170
  }
4167
5171
  async resolveApiRequest(path, method, init) {
4168
5172
  const { baseClient, options } = resolveScopedPlatformClient(this.api);