@agentuity/core 1.0.24 → 1.0.25

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.
@@ -0,0 +1,519 @@
1
+ import { FetchAdapter } from './adapter.ts';
2
+ import { buildUrl, toServiceException } from './_util.ts';
3
+ import { StructuredError } from '../error.ts';
4
+
5
+ function createTimeoutSignal(ms = 30_000): AbortSignal {
6
+ if (typeof AbortSignal.timeout === 'function') {
7
+ return AbortSignal.timeout(ms);
8
+ }
9
+ const controller = new AbortController();
10
+ const timer = setTimeout(() => controller.abort(), ms);
11
+ controller.signal.addEventListener('abort', () => clearTimeout(timer), { once: true });
12
+ return controller.signal;
13
+ }
14
+
15
+ export interface Webhook {
16
+ id: string;
17
+ created_at: string;
18
+ updated_at: string;
19
+ created_by: string;
20
+ name: string;
21
+ description: string | null;
22
+ url: string;
23
+ }
24
+
25
+ export interface WebhookDestination {
26
+ id: string;
27
+ webhook_id: string;
28
+ created_at: string;
29
+ updated_at: string;
30
+ created_by: string;
31
+ type: string;
32
+ config: Record<string, unknown>;
33
+ }
34
+
35
+ export interface WebhookReceipt {
36
+ id: string;
37
+ date: string;
38
+ webhook_id: string;
39
+ headers: Record<string, string>;
40
+ payload: unknown;
41
+ }
42
+
43
+ export interface WebhookDelivery {
44
+ id: string;
45
+ date: string;
46
+ webhook_id: string;
47
+ webhook_destination_id: string;
48
+ webhook_receipt_id: string;
49
+ status: 'pending' | 'success' | 'failed';
50
+ retries: number;
51
+ error: string | null;
52
+ response: Record<string, unknown> | null;
53
+ }
54
+
55
+ export interface CreateWebhookParams {
56
+ name: string;
57
+ description?: string;
58
+ }
59
+
60
+ export interface UpdateWebhookParams {
61
+ name?: string;
62
+ description?: string;
63
+ }
64
+
65
+ export interface CreateWebhookDestinationParams {
66
+ type: string;
67
+ config: Record<string, unknown>;
68
+ }
69
+
70
+ export interface WebhookListResult {
71
+ webhooks: Webhook[];
72
+ total: number;
73
+ }
74
+
75
+ export interface WebhookGetResult {
76
+ webhook: Webhook;
77
+ destinations: WebhookDestination[];
78
+ }
79
+
80
+ export interface WebhookCreateResult {
81
+ webhook: Webhook;
82
+ }
83
+
84
+ export interface UpdateWebhookResult {
85
+ webhook: Webhook;
86
+ }
87
+
88
+ export interface CreateDestinationResult {
89
+ destination: WebhookDestination;
90
+ }
91
+
92
+ export interface ListDestinationsResult {
93
+ destinations: WebhookDestination[];
94
+ }
95
+
96
+ export interface WebhookReceiptListResult {
97
+ receipts: WebhookReceipt[];
98
+ }
99
+
100
+ export interface WebhookDeliveryListResult {
101
+ deliveries: WebhookDelivery[];
102
+ }
103
+
104
+ interface WebhookSuccessResponse<T> {
105
+ success: true;
106
+ data: T;
107
+ }
108
+
109
+ interface WebhookErrorResponse {
110
+ success: false;
111
+ message: string;
112
+ }
113
+
114
+ type WebhookResponse<T> = WebhookSuccessResponse<T> | WebhookErrorResponse;
115
+
116
+ const WebhookResponseError = StructuredError('WebhookResponseError')<{
117
+ status: number;
118
+ }>();
119
+
120
+ export class WebhookService {
121
+ #adapter: FetchAdapter;
122
+ #baseUrl: string;
123
+
124
+ constructor(baseUrl: string, adapter: FetchAdapter) {
125
+ this.#adapter = adapter;
126
+ this.#baseUrl = baseUrl;
127
+ }
128
+
129
+ #unwrap<T>(raw: unknown): T {
130
+ if (raw !== null && typeof raw === 'object' && 'data' in raw) {
131
+ return (raw as Record<string, unknown>).data as T;
132
+ }
133
+ return raw as T;
134
+ }
135
+
136
+ async create(params: CreateWebhookParams): Promise<WebhookCreateResult> {
137
+ const url = buildUrl(this.#baseUrl, '/webhook/2026-02-24/create');
138
+ const signal = createTimeoutSignal();
139
+ const res = await this.#adapter.invoke<WebhookResponse<Webhook>>(url, {
140
+ method: 'POST',
141
+ signal,
142
+ body: JSON.stringify(params),
143
+ contentType: 'application/json',
144
+ telemetry: {
145
+ name: 'agentuity.webhook.create',
146
+ attributes: {
147
+ name: params.name,
148
+ },
149
+ },
150
+ });
151
+
152
+ if (res.ok) {
153
+ if (res.data.success) {
154
+ return { webhook: res.data.data };
155
+ }
156
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
157
+ }
158
+
159
+ throw await toServiceException('POST', url, res.response);
160
+ }
161
+
162
+ async list(params?: { limit?: number; offset?: number }): Promise<WebhookListResult> {
163
+ const qs = new URLSearchParams();
164
+ if (params?.limit !== undefined) {
165
+ qs.set('limit', String(params.limit));
166
+ }
167
+ if (params?.offset !== undefined) {
168
+ qs.set('offset', String(params.offset));
169
+ }
170
+
171
+ const path = qs.toString()
172
+ ? `/webhook/2026-02-24/list?${qs.toString()}`
173
+ : '/webhook/2026-02-24/list';
174
+ const url = buildUrl(this.#baseUrl, path);
175
+ const signal = createTimeoutSignal();
176
+ const res = await this.#adapter.invoke<WebhookResponse<Webhook[]>>(url, {
177
+ method: 'GET',
178
+ signal,
179
+ telemetry: {
180
+ name: 'agentuity.webhook.list',
181
+ attributes: {
182
+ limit: String(params?.limit ?? ''),
183
+ offset: String(params?.offset ?? ''),
184
+ },
185
+ },
186
+ });
187
+
188
+ if (res.ok) {
189
+ if (res.data.success) {
190
+ const unwrapped = this.#unwrap<Webhook[] | { data: Webhook[]; total: number }>(
191
+ res.data.data
192
+ );
193
+ if (Array.isArray(unwrapped)) {
194
+ return { webhooks: unwrapped, total: unwrapped.length };
195
+ }
196
+ const arr = Array.isArray(unwrapped.data) ? unwrapped.data : [];
197
+ return { webhooks: arr, total: unwrapped.total ?? arr.length };
198
+ }
199
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
200
+ }
201
+
202
+ throw await toServiceException('GET', url, res.response);
203
+ }
204
+
205
+ async get(webhookId: string): Promise<WebhookGetResult> {
206
+ const url = buildUrl(
207
+ this.#baseUrl,
208
+ `/webhook/2026-02-24/get/${encodeURIComponent(webhookId)}`
209
+ );
210
+ const signal = createTimeoutSignal();
211
+ const res = await this.#adapter.invoke<WebhookResponse<Webhook>>(url, {
212
+ method: 'GET',
213
+ signal,
214
+ telemetry: {
215
+ name: 'agentuity.webhook.get',
216
+ attributes: {
217
+ webhookId,
218
+ },
219
+ },
220
+ });
221
+
222
+ if (res.ok) {
223
+ if (res.data.success) {
224
+ const { destinations } = await this.listDestinations(webhookId);
225
+ return { webhook: res.data.data, destinations };
226
+ }
227
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
228
+ }
229
+
230
+ throw await toServiceException('GET', url, res.response);
231
+ }
232
+
233
+ async update(webhookId: string, params: UpdateWebhookParams): Promise<UpdateWebhookResult> {
234
+ const url = buildUrl(
235
+ this.#baseUrl,
236
+ `/webhook/2026-02-24/update/${encodeURIComponent(webhookId)}`
237
+ );
238
+ const signal = createTimeoutSignal();
239
+ const res = await this.#adapter.invoke<WebhookResponse<Webhook>>(url, {
240
+ method: 'PUT',
241
+ signal,
242
+ body: JSON.stringify(params),
243
+ contentType: 'application/json',
244
+ telemetry: {
245
+ name: 'agentuity.webhook.update',
246
+ attributes: {
247
+ webhookId,
248
+ },
249
+ },
250
+ });
251
+
252
+ if (res.ok) {
253
+ if (res.data.success) {
254
+ return { webhook: res.data.data };
255
+ }
256
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
257
+ }
258
+
259
+ throw await toServiceException('PUT', url, res.response);
260
+ }
261
+
262
+ async delete(webhookId: string): Promise<void> {
263
+ const url = buildUrl(
264
+ this.#baseUrl,
265
+ `/webhook/2026-02-24/delete/${encodeURIComponent(webhookId)}`
266
+ );
267
+ const signal = createTimeoutSignal();
268
+ const res = await this.#adapter.invoke<WebhookResponse<null>>(url, {
269
+ method: 'DELETE',
270
+ signal,
271
+ telemetry: {
272
+ name: 'agentuity.webhook.delete',
273
+ attributes: {
274
+ webhookId,
275
+ },
276
+ },
277
+ });
278
+
279
+ if (res.ok) {
280
+ if (res.data?.success !== false) {
281
+ return;
282
+ }
283
+ throw new WebhookResponseError({
284
+ status: res.response.status,
285
+ message: res.data?.message ?? 'Delete failed',
286
+ });
287
+ }
288
+
289
+ throw await toServiceException('DELETE', url, res.response);
290
+ }
291
+
292
+ async createDestination(
293
+ webhookId: string,
294
+ params: CreateWebhookDestinationParams
295
+ ): Promise<CreateDestinationResult> {
296
+ const url = buildUrl(
297
+ this.#baseUrl,
298
+ `/webhook/2026-02-24/destination-create/${encodeURIComponent(webhookId)}`
299
+ );
300
+ const signal = createTimeoutSignal();
301
+ const res = await this.#adapter.invoke<WebhookResponse<WebhookDestination>>(url, {
302
+ method: 'POST',
303
+ signal,
304
+ body: JSON.stringify(params),
305
+ contentType: 'application/json',
306
+ telemetry: {
307
+ name: 'agentuity.webhook.createDestination',
308
+ attributes: {
309
+ webhookId,
310
+ type: params.type,
311
+ },
312
+ },
313
+ });
314
+
315
+ if (res.ok) {
316
+ if (res.data.success) {
317
+ return { destination: res.data.data };
318
+ }
319
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
320
+ }
321
+
322
+ throw await toServiceException('POST', url, res.response);
323
+ }
324
+
325
+ async listDestinations(webhookId: string): Promise<ListDestinationsResult> {
326
+ const url = buildUrl(
327
+ this.#baseUrl,
328
+ `/webhook/2026-02-24/destination-list/${encodeURIComponent(webhookId)}`
329
+ );
330
+ const signal = createTimeoutSignal();
331
+ const res = await this.#adapter.invoke<WebhookResponse<WebhookDestination[]>>(url, {
332
+ method: 'GET',
333
+ signal,
334
+ telemetry: {
335
+ name: 'agentuity.webhook.listDestinations',
336
+ attributes: {
337
+ webhookId,
338
+ },
339
+ },
340
+ });
341
+
342
+ if (res.ok) {
343
+ if (res.data.success) {
344
+ return { destinations: Array.isArray(res.data.data) ? res.data.data : [] };
345
+ }
346
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
347
+ }
348
+
349
+ throw await toServiceException('GET', url, res.response);
350
+ }
351
+
352
+ async deleteDestination(webhookId: string, destinationId: string): Promise<void> {
353
+ const url = buildUrl(
354
+ this.#baseUrl,
355
+ `/webhook/2026-02-24/destination-delete/${encodeURIComponent(webhookId)}/${encodeURIComponent(destinationId)}`
356
+ );
357
+ const signal = createTimeoutSignal();
358
+ const res = await this.#adapter.invoke<WebhookResponse<null>>(url, {
359
+ method: 'DELETE',
360
+ signal,
361
+ telemetry: {
362
+ name: 'agentuity.webhook.deleteDestination',
363
+ attributes: {
364
+ webhookId,
365
+ destinationId,
366
+ },
367
+ },
368
+ });
369
+
370
+ if (res.ok) {
371
+ if (res.data?.success !== false) {
372
+ return;
373
+ }
374
+ throw new WebhookResponseError({
375
+ status: res.response.status,
376
+ message: res.data?.message ?? 'Delete destination failed',
377
+ });
378
+ }
379
+
380
+ throw await toServiceException('DELETE', url, res.response);
381
+ }
382
+
383
+ async listReceipts(
384
+ webhookId: string,
385
+ params?: { limit?: number; offset?: number }
386
+ ): Promise<WebhookReceiptListResult> {
387
+ const qs = new URLSearchParams();
388
+ if (params?.limit !== undefined) {
389
+ qs.set('limit', String(params.limit));
390
+ }
391
+ if (params?.offset !== undefined) {
392
+ qs.set('offset', String(params.offset));
393
+ }
394
+
395
+ const basePath = `/webhook/2026-02-24/receipt-list/${encodeURIComponent(webhookId)}`;
396
+ const path = qs.toString() ? `${basePath}?${qs.toString()}` : basePath;
397
+ const url = buildUrl(this.#baseUrl, path);
398
+ const signal = createTimeoutSignal();
399
+ const res = await this.#adapter.invoke<WebhookResponse<WebhookReceipt[]>>(url, {
400
+ method: 'GET',
401
+ signal,
402
+ telemetry: {
403
+ name: 'agentuity.webhook.listReceipts',
404
+ attributes: {
405
+ webhookId,
406
+ limit: String(params?.limit ?? ''),
407
+ offset: String(params?.offset ?? ''),
408
+ },
409
+ },
410
+ });
411
+
412
+ if (res.ok) {
413
+ if (res.data.success) {
414
+ return { receipts: Array.isArray(res.data.data) ? res.data.data : [] };
415
+ }
416
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
417
+ }
418
+
419
+ throw await toServiceException('GET', url, res.response);
420
+ }
421
+
422
+ async getReceipt(webhookId: string, receiptId: string): Promise<WebhookReceipt> {
423
+ const url = buildUrl(
424
+ this.#baseUrl,
425
+ `/webhook/2026-02-24/receipt-get/${encodeURIComponent(webhookId)}/${encodeURIComponent(receiptId)}`
426
+ );
427
+ const signal = createTimeoutSignal();
428
+ const res = await this.#adapter.invoke<WebhookResponse<WebhookReceipt>>(url, {
429
+ method: 'GET',
430
+ signal,
431
+ telemetry: {
432
+ name: 'agentuity.webhook.getReceipt',
433
+ attributes: {
434
+ webhookId,
435
+ receiptId,
436
+ },
437
+ },
438
+ });
439
+
440
+ if (res.ok) {
441
+ if (res.data.success) {
442
+ return res.data.data;
443
+ }
444
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
445
+ }
446
+
447
+ throw await toServiceException('GET', url, res.response);
448
+ }
449
+
450
+ async listDeliveries(
451
+ webhookId: string,
452
+ params?: { limit?: number; offset?: number }
453
+ ): Promise<WebhookDeliveryListResult> {
454
+ const qs = new URLSearchParams();
455
+ if (params?.limit !== undefined) {
456
+ qs.set('limit', String(params.limit));
457
+ }
458
+ if (params?.offset !== undefined) {
459
+ qs.set('offset', String(params.offset));
460
+ }
461
+
462
+ const basePath = `/webhook/2026-02-24/delivery-list/${encodeURIComponent(webhookId)}`;
463
+ const path = qs.toString() ? `${basePath}?${qs.toString()}` : basePath;
464
+ const url = buildUrl(this.#baseUrl, path);
465
+ const signal = createTimeoutSignal();
466
+ const res = await this.#adapter.invoke<WebhookResponse<WebhookDelivery[]>>(url, {
467
+ method: 'GET',
468
+ signal,
469
+ telemetry: {
470
+ name: 'agentuity.webhook.listDeliveries',
471
+ attributes: {
472
+ webhookId,
473
+ limit: String(params?.limit ?? ''),
474
+ offset: String(params?.offset ?? ''),
475
+ },
476
+ },
477
+ });
478
+
479
+ if (res.ok) {
480
+ if (res.data.success) {
481
+ return { deliveries: Array.isArray(res.data.data) ? res.data.data : [] };
482
+ }
483
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
484
+ }
485
+
486
+ throw await toServiceException('GET', url, res.response);
487
+ }
488
+
489
+ async retryDelivery(webhookId: string, deliveryId: string): Promise<void> {
490
+ const url = buildUrl(
491
+ this.#baseUrl,
492
+ `/webhook/2026-02-24/delivery-retry/${encodeURIComponent(webhookId)}/${encodeURIComponent(deliveryId)}`
493
+ );
494
+ const signal = createTimeoutSignal();
495
+ const res = await this.#adapter.invoke<WebhookResponse<null>>(url, {
496
+ method: 'POST',
497
+ signal,
498
+ telemetry: {
499
+ name: 'agentuity.webhook.retryDelivery',
500
+ attributes: {
501
+ webhookId,
502
+ deliveryId,
503
+ },
504
+ },
505
+ });
506
+
507
+ if (res.ok) {
508
+ if (res.data?.success !== false) {
509
+ return;
510
+ }
511
+ throw new WebhookResponseError({
512
+ status: res.response.status,
513
+ message: res.data?.message ?? 'Retry delivery failed',
514
+ });
515
+ }
516
+
517
+ throw await toServiceException('POST', url, res.response);
518
+ }
519
+ }