@amigo-ai/platform-sdk 0.23.0 → 0.24.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 CHANGED
@@ -66,7 +66,6 @@ All workspace-scoped resources also expose `withOptions(options)`.
66
66
 
67
67
  ### `workspaces`
68
68
 
69
- - `create`
70
69
  - `createSelfService`
71
70
  - `list`
72
71
  - `listAutoPaging`
@@ -418,6 +417,10 @@ All workspace-scoped resources also expose `withOptions(options)`.
418
417
  - `getHipaa`
419
418
  - `getAccessReview`
420
419
 
420
+ ### `events`
421
+
422
+ - `subscribeToWorkspace`
423
+
421
424
  ### `functions`
422
425
 
423
426
  - `list`
package/dist/index.cjs CHANGED
@@ -882,14 +882,6 @@ function resolveScopedPlatformClient(client) {
882
882
 
883
883
  // src/resources/workspaces.ts
884
884
  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
885
  /** Create a workspace for the authenticated user and attach owner access */
894
886
  async createSelfService(body) {
895
887
  return extractData(
@@ -3352,6 +3344,318 @@ var ComplianceResource = class extends WorkspaceScopedResource {
3352
3344
  }
3353
3345
  };
3354
3346
 
3347
+ // src/resources/events.ts
3348
+ var DEFAULT_INITIAL_DELAY_MS = 3e3;
3349
+ var DEFAULT_MAX_DELAY_MS = 3e4;
3350
+ var DEFAULT_MAX_RECONNECTS = 10;
3351
+ var MAX_FRAME_BYTES = 1048576;
3352
+ var EventsResource = class extends WorkspaceScopedResource {
3353
+ constructor(client, workspaceId2) {
3354
+ super(client, workspaceId2);
3355
+ }
3356
+ /**
3357
+ * Subscribe to the workspace event stream.
3358
+ *
3359
+ * Establishes an SSE connection to ``/v1/{workspace_id}/events/stream``
3360
+ * and invokes ``onEvent`` once per typed {@link WorkspaceSSEEvent}.
3361
+ * Unrecoverable failures (auth errors, exhausted reconnect budget,
3362
+ * caller abort) surface through ``onError``.
3363
+ *
3364
+ * Reconnection is automatic: on a network drop or 5xx, the helper
3365
+ * backs off (initial delay derived from the ``retry:`` directive,
3366
+ * default 3s, doubling with full jitter up to ``maxDelayMs``) and
3367
+ * resumes with the most recently seen ``Last-Event-ID``. The platform
3368
+ * buffers 5 minutes of events for gapless replay.
3369
+ *
3370
+ * @returns a {@link SubscriptionHandle} for cleanup. Aborting the
3371
+ * caller's ``signal`` is equivalent to calling ``unsubscribe()``.
3372
+ */
3373
+ subscribeToWorkspace(options) {
3374
+ const localController = new AbortController();
3375
+ const cleanups = [];
3376
+ if (options.signal) {
3377
+ if (options.signal.aborted) {
3378
+ localController.abort(options.signal.reason);
3379
+ } else {
3380
+ const onAbort = () => localController.abort(options.signal?.reason);
3381
+ options.signal.addEventListener("abort", onAbort, { once: true });
3382
+ cleanups.push(() => options.signal?.removeEventListener("abort", onAbort));
3383
+ }
3384
+ }
3385
+ const done = runSubscription(
3386
+ this.client,
3387
+ this.workspaceId,
3388
+ options,
3389
+ localController.signal
3390
+ ).finally(() => {
3391
+ for (const cleanup of cleanups) cleanup();
3392
+ });
3393
+ return {
3394
+ done,
3395
+ unsubscribe: () => localController.abort()
3396
+ };
3397
+ }
3398
+ };
3399
+ async function runSubscription(client, workspaceId2, options, signal) {
3400
+ let lastEventId = options.lastEventId;
3401
+ let attempt = 0;
3402
+ let delayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
3403
+ const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
3404
+ const maxReconnects = options.maxReconnects ?? DEFAULT_MAX_RECONNECTS;
3405
+ let errorReported = false;
3406
+ const reportError = (error) => {
3407
+ if (errorReported) return;
3408
+ errorReported = true;
3409
+ try {
3410
+ options.onError?.(error);
3411
+ } catch {
3412
+ }
3413
+ };
3414
+ while (!signal.aborted) {
3415
+ if (attempt > 0) {
3416
+ try {
3417
+ options.onReconnect?.(attempt);
3418
+ } catch {
3419
+ }
3420
+ }
3421
+ let outcome;
3422
+ try {
3423
+ outcome = await runOneConnection({
3424
+ client,
3425
+ workspaceId: workspaceId2,
3426
+ lastEventId,
3427
+ signal,
3428
+ onEvent: options.onEvent,
3429
+ onIdAdvance: (id) => {
3430
+ lastEventId = id;
3431
+ },
3432
+ onRetryDirective: (ms) => {
3433
+ delayMs = clampDelay(ms, options.initialDelayMs, maxDelayMs);
3434
+ }
3435
+ });
3436
+ } catch (err) {
3437
+ reportError(err instanceof Error ? err : new Error(String(err)));
3438
+ return;
3439
+ }
3440
+ if (signal.aborted || outcome.kind === "aborted") {
3441
+ return;
3442
+ }
3443
+ if (outcome.kind === "auth-error") {
3444
+ reportError(outcome.error);
3445
+ return;
3446
+ }
3447
+ if (attempt >= maxReconnects) {
3448
+ reportError(
3449
+ new Error(
3450
+ `SSE subscription exhausted reconnect budget (${maxReconnects}): ${outcome.reason}`
3451
+ )
3452
+ );
3453
+ return;
3454
+ }
3455
+ attempt += 1;
3456
+ const sleepMs = jitter(delayMs);
3457
+ delayMs = Math.min(delayMs * 2, maxDelayMs);
3458
+ const slept = await abortableSleep(sleepMs, signal);
3459
+ if (!slept) return;
3460
+ }
3461
+ }
3462
+ async function runOneConnection(args) {
3463
+ const headers = { Accept: "text/event-stream" };
3464
+ if (args.lastEventId !== void 0) {
3465
+ headers["Last-Event-ID"] = args.lastEventId;
3466
+ }
3467
+ let result;
3468
+ try {
3469
+ result = await args.client.GET("/v1/{workspace_id}/events/stream", {
3470
+ params: { path: { workspace_id: args.workspaceId } },
3471
+ headers,
3472
+ parseAs: "stream",
3473
+ signal: args.signal
3474
+ });
3475
+ } catch (err) {
3476
+ if (args.signal.aborted) return { kind: "aborted" };
3477
+ const error = err instanceof Error ? err : new Error(String(err));
3478
+ const status = readStatus(error);
3479
+ if (status === 401 || status === 403) {
3480
+ return { kind: "auth-error", error };
3481
+ }
3482
+ return { kind: "transport-error", reason: error.message };
3483
+ }
3484
+ if (result.error !== void 0) {
3485
+ return { kind: "transport-error", reason: `API error: ${safeStringify(result.error)}` };
3486
+ }
3487
+ const body = result.data;
3488
+ if (!(body instanceof ReadableStream)) {
3489
+ return { kind: "transport-error", reason: "Expected ReadableStream body for SSE" };
3490
+ }
3491
+ try {
3492
+ for await (const frame of parseSSEFrames2(body, args.signal)) {
3493
+ if (args.signal.aborted) return { kind: "aborted" };
3494
+ if (frame.retry !== void 0) {
3495
+ args.onRetryDirective(frame.retry);
3496
+ }
3497
+ if (frame.id !== void 0) {
3498
+ args.onIdAdvance(frame.id);
3499
+ }
3500
+ if (frame.event && frame.data !== void 0) {
3501
+ const event = parseWorkspaceFrame(frame.event, frame.data);
3502
+ if (event) {
3503
+ try {
3504
+ args.onEvent(event);
3505
+ } catch {
3506
+ }
3507
+ }
3508
+ }
3509
+ }
3510
+ } catch (err) {
3511
+ if (args.signal.aborted) return { kind: "aborted" };
3512
+ const reason = err instanceof Error ? err.message : String(err);
3513
+ return { kind: "transport-error", reason };
3514
+ }
3515
+ if (args.signal.aborted) return { kind: "aborted" };
3516
+ return { kind: "transport-error", reason: "Stream closed by server" };
3517
+ }
3518
+ async function* parseSSEFrames2(stream, signal) {
3519
+ const reader = stream.getReader();
3520
+ const decoder = new TextDecoder();
3521
+ let buffer = "";
3522
+ let cancelled = false;
3523
+ const onAbort = () => {
3524
+ if (cancelled) return;
3525
+ cancelled = true;
3526
+ void reader.cancel().catch(() => {
3527
+ });
3528
+ };
3529
+ let removeAbortHandler;
3530
+ if (signal.aborted) {
3531
+ onAbort();
3532
+ } else {
3533
+ signal.addEventListener("abort", onAbort, { once: true });
3534
+ removeAbortHandler = () => signal.removeEventListener("abort", onAbort);
3535
+ }
3536
+ function* drain(text) {
3537
+ buffer += text;
3538
+ if (buffer.length > MAX_FRAME_BYTES) {
3539
+ throw new Error(`SSE frame buffer exceeded ${MAX_FRAME_BYTES} bytes without terminator`);
3540
+ }
3541
+ while (true) {
3542
+ const idx = findFrameTerminator2(buffer);
3543
+ if (idx === null) break;
3544
+ const block = buffer.slice(0, idx.terminatorStart);
3545
+ buffer = buffer.slice(idx.terminatorEnd);
3546
+ const frame = parseSSEBlock2(block);
3547
+ if (frame) yield frame;
3548
+ }
3549
+ }
3550
+ try {
3551
+ while (true) {
3552
+ const { done, value } = await reader.read();
3553
+ if (done) break;
3554
+ yield* drain(decoder.decode(value, { stream: true }));
3555
+ }
3556
+ yield* drain(decoder.decode());
3557
+ if (buffer.trim().length > 0) {
3558
+ const frame = parseSSEBlock2(buffer);
3559
+ if (frame) yield frame;
3560
+ buffer = "";
3561
+ }
3562
+ } finally {
3563
+ removeAbortHandler?.();
3564
+ try {
3565
+ reader.releaseLock();
3566
+ } catch {
3567
+ }
3568
+ }
3569
+ }
3570
+ function findFrameTerminator2(s) {
3571
+ const lf = s.indexOf("\n\n");
3572
+ const crlf = s.indexOf("\r\n\r\n");
3573
+ if (lf < 0 && crlf < 0) return null;
3574
+ if (lf < 0) return { terminatorStart: crlf, terminatorEnd: crlf + 4 };
3575
+ if (crlf < 0) return { terminatorStart: lf, terminatorEnd: lf + 2 };
3576
+ return lf < crlf ? { terminatorStart: lf, terminatorEnd: lf + 2 } : { terminatorStart: crlf, terminatorEnd: crlf + 4 };
3577
+ }
3578
+ function parseSSEBlock2(block) {
3579
+ let event = "";
3580
+ let id;
3581
+ let retry;
3582
+ const dataLines = [];
3583
+ for (const line of block.split(/\r?\n/)) {
3584
+ if (line === "" || line.startsWith(":")) continue;
3585
+ const colon = line.indexOf(":");
3586
+ const field = colon < 0 ? line : line.slice(0, colon);
3587
+ let value = colon < 0 ? "" : line.slice(colon + 1);
3588
+ if (value.startsWith(" ")) value = value.slice(1);
3589
+ if (field === "event") {
3590
+ event = value;
3591
+ } else if (field === "data") {
3592
+ dataLines.push(value);
3593
+ } else if (field === "id") {
3594
+ id = value;
3595
+ } else if (field === "retry") {
3596
+ const parsed = Number.parseInt(value, 10);
3597
+ if (Number.isFinite(parsed) && parsed >= 0) retry = parsed;
3598
+ }
3599
+ }
3600
+ if (!event && dataLines.length === 0 && id === void 0 && retry === void 0) {
3601
+ return null;
3602
+ }
3603
+ return {
3604
+ event,
3605
+ data: dataLines.length > 0 ? dataLines.join("\n") : void 0,
3606
+ id,
3607
+ retry
3608
+ };
3609
+ }
3610
+ function parseWorkspaceFrame(eventName, dataJson) {
3611
+ let payload;
3612
+ try {
3613
+ payload = JSON.parse(dataJson);
3614
+ } catch {
3615
+ return null;
3616
+ }
3617
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return null;
3618
+ return {
3619
+ ...payload,
3620
+ event_type: eventName
3621
+ };
3622
+ }
3623
+ function readStatus(error) {
3624
+ if (typeof error !== "object" || error === null) return void 0;
3625
+ const status = error.statusCode;
3626
+ return typeof status === "number" ? status : void 0;
3627
+ }
3628
+ function safeStringify(value) {
3629
+ try {
3630
+ return JSON.stringify(value);
3631
+ } catch {
3632
+ return String(value);
3633
+ }
3634
+ }
3635
+ function jitter(ms) {
3636
+ return Math.floor(Math.random() * Math.max(1, ms));
3637
+ }
3638
+ function clampDelay(ms, floor, ceiling) {
3639
+ const lo = floor ?? DEFAULT_INITIAL_DELAY_MS;
3640
+ if (!Number.isFinite(ms) || ms <= 0) return lo;
3641
+ return Math.min(Math.max(ms, lo), ceiling);
3642
+ }
3643
+ async function abortableSleep(ms, signal) {
3644
+ if (signal.aborted) return false;
3645
+ return new Promise((resolve) => {
3646
+ const timer = setTimeout(() => {
3647
+ signal.removeEventListener("abort", onAbort);
3648
+ resolve(true);
3649
+ }, ms);
3650
+ const onAbort = () => {
3651
+ clearTimeout(timer);
3652
+ signal.removeEventListener("abort", onAbort);
3653
+ resolve(false);
3654
+ };
3655
+ signal.addEventListener("abort", onAbort, { once: true });
3656
+ });
3657
+ }
3658
+
3355
3659
  // src/resources/functions.ts
3356
3660
  var FunctionsResource = class extends WorkspaceScopedResource {
3357
3661
  async list() {
@@ -4067,6 +4371,7 @@ var AmigoClient = class _AmigoClient {
4067
4371
  webhookDestinations;
4068
4372
  safety;
4069
4373
  compliance;
4374
+ events;
4070
4375
  functions;
4071
4376
  /** @internal — exposed for path-level type inference in GET/POST/PUT/etc. */
4072
4377
  api;
@@ -4162,6 +4467,7 @@ var AmigoClient = class _AmigoClient {
4162
4467
  mutable.webhookDestinations = new WebhookDestinationsResource(client, workspaceId2);
4163
4468
  mutable.safety = new SafetyResource(client, workspaceId2);
4164
4469
  mutable.compliance = new ComplianceResource(client, workspaceId2);
4470
+ mutable.events = new EventsResource(client, workspaceId2);
4165
4471
  mutable.functions = new FunctionsResource(client, workspaceId2);
4166
4472
  }
4167
4473
  async resolveApiRequest(path, method, init) {