@agentuity/core 1.0.21 → 1.0.23

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,663 @@
1
+ import { FetchAdapter } from './adapter.ts';
2
+ import { buildUrl, toServiceException } from './_util.ts';
3
+ import { safeStringify } from '../json.ts';
4
+
5
+ /**
6
+ * An email address registered with the Agentuity email service
7
+ */
8
+ export interface EmailAddress {
9
+ id: string;
10
+ email: string;
11
+ project_id?: string;
12
+ provider?: string;
13
+ config?: Record<string, unknown>;
14
+ created_by?: string;
15
+ created_at: string;
16
+ updated_at?: string;
17
+ }
18
+
19
+ /**
20
+ * A destination configuration for an email address
21
+ */
22
+ export interface EmailDestination {
23
+ id: string;
24
+ type: string;
25
+ config?: Record<string, unknown>;
26
+ created_at: string;
27
+ updated_at?: string;
28
+ }
29
+
30
+ /**
31
+ * An inbound email message
32
+ */
33
+ export interface EmailInbound {
34
+ id: string;
35
+ from: string;
36
+ to: string;
37
+ subject?: string;
38
+ text?: string;
39
+ status?: string;
40
+ received_at?: string;
41
+ }
42
+
43
+ /**
44
+ * An outbound email message
45
+ */
46
+ export interface EmailOutbound {
47
+ id: string;
48
+ from: string;
49
+ to: string;
50
+ subject?: string;
51
+ text?: string;
52
+ html?: string;
53
+ status?: string;
54
+ error?: string;
55
+ sent_at?: string;
56
+ created_at?: string;
57
+ updated_at?: string;
58
+ }
59
+
60
+ /**
61
+ * An email attachment
62
+ */
63
+ export interface EmailAttachment {
64
+ /**
65
+ * The filename for the attachment
66
+ */
67
+ filename: string;
68
+
69
+ /**
70
+ * The base64-encoded content of the attachment
71
+ */
72
+ content: string;
73
+
74
+ /**
75
+ * The MIME content type of the attachment
76
+ */
77
+ contentType?: string;
78
+ }
79
+
80
+ /**
81
+ * Parameters for sending an email
82
+ */
83
+ export interface EmailSendParams {
84
+ /**
85
+ * The sender email address (must be owned by the organization)
86
+ */
87
+ from: string;
88
+
89
+ /**
90
+ * The recipient email addresses
91
+ */
92
+ to: string[];
93
+
94
+ /**
95
+ * The email subject
96
+ */
97
+ subject: string;
98
+
99
+ /**
100
+ * Plain text email body
101
+ */
102
+ text?: string;
103
+
104
+ /**
105
+ * HTML email body
106
+ */
107
+ html?: string;
108
+
109
+ /**
110
+ * File attachments
111
+ */
112
+ attachments?: EmailAttachment[];
113
+ }
114
+
115
+ /**
116
+ * Email service for managing email addresses, destinations, and sending/receiving emails
117
+ */
118
+ export interface EmailService {
119
+ /**
120
+ * Create a new email address
121
+ *
122
+ * @param localPart - the local part of the email address (before the @)
123
+ * @returns the created email address
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const address = await email.createAddress('support');
128
+ * console.log('Created:', address.email);
129
+ * ```
130
+ */
131
+ createAddress(localPart: string): Promise<EmailAddress>;
132
+
133
+ /**
134
+ * List all email addresses
135
+ *
136
+ * @returns array of email addresses
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * const addresses = await email.listAddresses();
141
+ * for (const addr of addresses) {
142
+ * console.log(addr.email);
143
+ * }
144
+ * ```
145
+ */
146
+ listAddresses(): Promise<EmailAddress[]>;
147
+
148
+ /**
149
+ * Get an email address by ID
150
+ *
151
+ * @param id - the email address ID
152
+ * @returns the email address or null if not found
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * const address = await email.getAddress('addr_123');
157
+ * if (address) {
158
+ * console.log('Found:', address.email);
159
+ * }
160
+ * ```
161
+ */
162
+ getAddress(id: string): Promise<EmailAddress | null>;
163
+
164
+ /**
165
+ * Delete an email address
166
+ *
167
+ * @param id - the email address ID
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * await email.deleteAddress('addr_123');
172
+ * ```
173
+ */
174
+ deleteAddress(id: string): Promise<void>;
175
+
176
+ /**
177
+ * Create a destination for an email address
178
+ *
179
+ * @param addressId - the email address ID
180
+ * @param type - the destination type (e.g., 'url', 'agent')
181
+ * @param config - the destination configuration
182
+ * @returns the created destination
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const dest = await email.createDestination('addr_123', 'url', {
187
+ * url: 'https://example.com/webhook',
188
+ * });
189
+ * console.log('Created destination:', dest.id);
190
+ * ```
191
+ */
192
+ createDestination(
193
+ addressId: string,
194
+ type: string,
195
+ config: Record<string, unknown>
196
+ ): Promise<EmailDestination>;
197
+
198
+ /**
199
+ * List destinations for an email address
200
+ *
201
+ * @param addressId - the email address ID
202
+ * @returns array of destinations
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const destinations = await email.listDestinations('addr_123');
207
+ * for (const dest of destinations) {
208
+ * console.log(`${dest.type}: ${dest.id}`);
209
+ * }
210
+ * ```
211
+ */
212
+ listDestinations(addressId: string): Promise<EmailDestination[]>;
213
+
214
+ /**
215
+ * Delete a destination from an email address
216
+ *
217
+ * @param addressId - the email address ID
218
+ * @param destinationId - the destination ID
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * await email.deleteDestination('addr_123', 'dest_456');
223
+ * ```
224
+ */
225
+ deleteDestination(addressId: string, destinationId: string): Promise<void>;
226
+
227
+ /**
228
+ * Send an email
229
+ *
230
+ * @param params - the send parameters
231
+ * @returns the outbound email record
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * const result = await email.send({
236
+ * from: 'support@myapp.agentuity.email',
237
+ * to: ['user@example.com'],
238
+ * subject: 'Welcome!',
239
+ * text: 'Welcome to our platform.',
240
+ * html: '<h1>Welcome!</h1><p>Welcome to our platform.</p>',
241
+ * });
242
+ * console.log('Sent:', result.id, 'Status:', result.status);
243
+ * ```
244
+ */
245
+ send(params: EmailSendParams): Promise<EmailOutbound>;
246
+
247
+ /**
248
+ * List inbound emails
249
+ *
250
+ * @param addressId - optional email address ID to filter by
251
+ * @returns array of inbound emails
252
+ *
253
+ * @example
254
+ * ```typescript
255
+ * const inbound = await email.listInbound('addr_123');
256
+ * for (const msg of inbound) {
257
+ * console.log(`From: ${msg.from}, Subject: ${msg.subject}`);
258
+ * }
259
+ * ```
260
+ */
261
+ listInbound(addressId?: string): Promise<EmailInbound[]>;
262
+
263
+ /**
264
+ * Get an inbound email by ID
265
+ *
266
+ * @param id - the inbound email ID
267
+ * @returns the inbound email or null if not found
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const msg = await email.getInbound('inb_123');
272
+ * if (msg) {
273
+ * console.log('Subject:', msg.subject);
274
+ * }
275
+ * ```
276
+ */
277
+ getInbound(id: string): Promise<EmailInbound | null>;
278
+
279
+ /**
280
+ * List outbound emails
281
+ *
282
+ * @param addressId - optional email address ID to filter by
283
+ * @returns array of outbound emails
284
+ *
285
+ * @example
286
+ * ```typescript
287
+ * const outbound = await email.listOutbound('addr_123');
288
+ * for (const msg of outbound) {
289
+ * console.log(`To: ${msg.to}, Status: ${msg.status}`);
290
+ * }
291
+ * ```
292
+ */
293
+ listOutbound(addressId?: string): Promise<EmailOutbound[]>;
294
+
295
+ /**
296
+ * Get an outbound email by ID
297
+ *
298
+ * @param id - the outbound email ID
299
+ * @returns the outbound email or null if not found
300
+ *
301
+ * @example
302
+ * ```typescript
303
+ * const msg = await email.getOutbound('out_123');
304
+ * if (msg) {
305
+ * console.log('Status:', msg.status);
306
+ * }
307
+ * ```
308
+ */
309
+ getOutbound(id: string): Promise<EmailOutbound | null>;
310
+ }
311
+
312
+ /**
313
+ * Unwrap a Catalyst API response payload.
314
+ * Handles both `{ key: data }` and `{ data: { key: data } }` response formats.
315
+ */
316
+ function unwrap<T>(payload: unknown, key: string): T {
317
+ if (typeof payload === 'object' && payload !== null) {
318
+ const obj = payload as Record<string, unknown>;
319
+ if (key in obj) {
320
+ return obj[key] as T;
321
+ }
322
+ if ('data' in obj && typeof obj.data === 'object' && obj.data !== null) {
323
+ const data = obj.data as Record<string, unknown>;
324
+ if (key in data) {
325
+ return data[key] as T;
326
+ }
327
+ return data as T;
328
+ }
329
+ }
330
+ return payload as T;
331
+ }
332
+
333
+ export class EmailStorageService implements EmailService {
334
+ #adapter: FetchAdapter;
335
+ #baseUrl: string;
336
+
337
+ constructor(baseUrl: string, adapter: FetchAdapter) {
338
+ this.#adapter = adapter;
339
+ this.#baseUrl = baseUrl;
340
+ }
341
+
342
+ async createAddress(localPart: string): Promise<EmailAddress> {
343
+ const url = buildUrl(this.#baseUrl, '/email/2025-03-17/addresses');
344
+ const signal = AbortSignal.timeout(30_000);
345
+ const res = await this.#adapter.invoke<unknown>(url, {
346
+ method: 'POST',
347
+ body: safeStringify({ local_part: localPart }),
348
+ contentType: 'application/json',
349
+ signal,
350
+ telemetry: {
351
+ name: 'agentuity.email.createAddress',
352
+ attributes: {
353
+ localPart,
354
+ },
355
+ },
356
+ });
357
+ if (res.ok) {
358
+ return unwrap<EmailAddress>(res.data, 'address');
359
+ }
360
+ throw await toServiceException('POST', url, res.response);
361
+ }
362
+
363
+ async listAddresses(): Promise<EmailAddress[]> {
364
+ const url = buildUrl(this.#baseUrl, '/email/2025-03-17/addresses');
365
+ const signal = AbortSignal.timeout(30_000);
366
+ const res = await this.#adapter.invoke<unknown>(url, {
367
+ method: 'GET',
368
+ signal,
369
+ telemetry: {
370
+ name: 'agentuity.email.listAddresses',
371
+ attributes: {},
372
+ },
373
+ });
374
+ if (res.response.status === 404) {
375
+ return [];
376
+ }
377
+ if (res.ok) {
378
+ const items = unwrap<unknown>(res.data, 'addresses');
379
+ return Array.isArray(items) ? (items as EmailAddress[]) : [];
380
+ }
381
+ throw await toServiceException('GET', url, res.response);
382
+ }
383
+
384
+ async getAddress(id: string): Promise<EmailAddress | null> {
385
+ const url = buildUrl(
386
+ this.#baseUrl,
387
+ `/email/2025-03-17/addresses/${encodeURIComponent(id)}`
388
+ );
389
+ const signal = AbortSignal.timeout(30_000);
390
+ const res = await this.#adapter.invoke<unknown>(url, {
391
+ method: 'GET',
392
+ signal,
393
+ telemetry: {
394
+ name: 'agentuity.email.getAddress',
395
+ attributes: {
396
+ id,
397
+ },
398
+ },
399
+ });
400
+ if (res.response.status === 404) {
401
+ return null;
402
+ }
403
+ if (res.ok) {
404
+ return unwrap<EmailAddress>(res.data, 'address');
405
+ }
406
+ throw await toServiceException('GET', url, res.response);
407
+ }
408
+
409
+ async deleteAddress(id: string): Promise<void> {
410
+ const url = buildUrl(
411
+ this.#baseUrl,
412
+ `/email/2025-03-17/addresses/${encodeURIComponent(id)}`
413
+ );
414
+ const signal = AbortSignal.timeout(30_000);
415
+ const res = await this.#adapter.invoke<unknown>(url, {
416
+ method: 'DELETE',
417
+ signal,
418
+ telemetry: {
419
+ name: 'agentuity.email.deleteAddress',
420
+ attributes: {
421
+ id,
422
+ },
423
+ },
424
+ });
425
+ if (res.ok || res.response.status === 404) {
426
+ return;
427
+ }
428
+ throw await toServiceException('DELETE', url, res.response);
429
+ }
430
+
431
+ async createDestination(
432
+ addressId: string,
433
+ type: string,
434
+ config: Record<string, unknown>
435
+ ): Promise<EmailDestination> {
436
+ const url = buildUrl(
437
+ this.#baseUrl,
438
+ `/email/2025-03-17/addresses/${encodeURIComponent(addressId)}/destinations`
439
+ );
440
+ const signal = AbortSignal.timeout(30_000);
441
+ const res = await this.#adapter.invoke<unknown>(url, {
442
+ method: 'POST',
443
+ body: safeStringify({ type, config }),
444
+ contentType: 'application/json',
445
+ signal,
446
+ telemetry: {
447
+ name: 'agentuity.email.createDestination',
448
+ attributes: {
449
+ addressId,
450
+ type,
451
+ },
452
+ },
453
+ });
454
+ if (res.ok) {
455
+ return unwrap<EmailDestination>(res.data, 'destination');
456
+ }
457
+ throw await toServiceException('POST', url, res.response);
458
+ }
459
+
460
+ async listDestinations(addressId: string): Promise<EmailDestination[]> {
461
+ const url = buildUrl(
462
+ this.#baseUrl,
463
+ `/email/2025-03-17/addresses/${encodeURIComponent(addressId)}/destinations`
464
+ );
465
+ const signal = AbortSignal.timeout(30_000);
466
+ const res = await this.#adapter.invoke<unknown>(url, {
467
+ method: 'GET',
468
+ signal,
469
+ telemetry: {
470
+ name: 'agentuity.email.listDestinations',
471
+ attributes: {
472
+ addressId,
473
+ },
474
+ },
475
+ });
476
+ if (res.response.status === 404) {
477
+ return [];
478
+ }
479
+ if (res.ok) {
480
+ const items = unwrap<unknown>(res.data, 'destinations');
481
+ return Array.isArray(items) ? (items as EmailDestination[]) : [];
482
+ }
483
+ throw await toServiceException('GET', url, res.response);
484
+ }
485
+
486
+ async deleteDestination(addressId: string, destinationId: string): Promise<void> {
487
+ const url = buildUrl(
488
+ this.#baseUrl,
489
+ `/email/2025-03-17/addresses/${encodeURIComponent(addressId)}/destinations/${encodeURIComponent(destinationId)}`
490
+ );
491
+ const signal = AbortSignal.timeout(30_000);
492
+ const res = await this.#adapter.invoke<unknown>(url, {
493
+ method: 'DELETE',
494
+ signal,
495
+ telemetry: {
496
+ name: 'agentuity.email.deleteDestination',
497
+ attributes: {
498
+ addressId,
499
+ destinationId,
500
+ },
501
+ },
502
+ });
503
+ if (res.ok || res.response.status === 404) {
504
+ return;
505
+ }
506
+ throw await toServiceException('DELETE', url, res.response);
507
+ }
508
+
509
+ async send(params: EmailSendParams): Promise<EmailOutbound> {
510
+ const url = buildUrl(this.#baseUrl, '/email/2025-03-17/outbound/send');
511
+ const signal = AbortSignal.timeout(30_000);
512
+
513
+ // Transform attachments to API format (snake_case)
514
+ const body: Record<string, unknown> = {
515
+ from: params.from,
516
+ to: params.to,
517
+ subject: params.subject,
518
+ };
519
+ if (params.text !== undefined) {
520
+ body.text = params.text;
521
+ }
522
+ if (params.html !== undefined) {
523
+ body.html = params.html;
524
+ }
525
+ if (params.attachments && params.attachments.length > 0) {
526
+ body.attachments = params.attachments.map((a) => ({
527
+ filename: a.filename,
528
+ content_base64: a.content,
529
+ ...(a.contentType && { content_type: a.contentType }),
530
+ }));
531
+ }
532
+
533
+ const res = await this.#adapter.invoke<unknown>(url, {
534
+ method: 'POST',
535
+ body: safeStringify(body),
536
+ contentType: 'application/json',
537
+ signal,
538
+ telemetry: {
539
+ name: 'agentuity.email.send',
540
+ attributes: {
541
+ from: params.from,
542
+ toCount: String(params.to.length),
543
+ },
544
+ },
545
+ });
546
+ if (res.ok) {
547
+ return unwrap<EmailOutbound>(res.data, 'outbound');
548
+ }
549
+ throw await toServiceException('POST', url, res.response);
550
+ }
551
+
552
+ async listInbound(addressId?: string): Promise<EmailInbound[]> {
553
+ const queryParams = new URLSearchParams();
554
+ if (addressId) {
555
+ queryParams.set('address_id', addressId);
556
+ }
557
+ const queryString = queryParams.toString();
558
+ const url = buildUrl(
559
+ this.#baseUrl,
560
+ `/email/2025-03-17/inbound${queryString ? `?${queryString}` : ''}`
561
+ );
562
+ const signal = AbortSignal.timeout(30_000);
563
+ const res = await this.#adapter.invoke<unknown>(url, {
564
+ method: 'GET',
565
+ signal,
566
+ telemetry: {
567
+ name: 'agentuity.email.listInbound',
568
+ attributes: {
569
+ ...(addressId && { addressId }),
570
+ },
571
+ },
572
+ });
573
+ if (res.response.status === 404) {
574
+ return [];
575
+ }
576
+ if (res.ok) {
577
+ const items = unwrap<unknown>(res.data, 'inbound');
578
+ return Array.isArray(items) ? (items as EmailInbound[]) : [];
579
+ }
580
+ throw await toServiceException('GET', url, res.response);
581
+ }
582
+
583
+ async getInbound(id: string): Promise<EmailInbound | null> {
584
+ const url = buildUrl(
585
+ this.#baseUrl,
586
+ `/email/2025-03-17/inbound/${encodeURIComponent(id)}`
587
+ );
588
+ const signal = AbortSignal.timeout(30_000);
589
+ const res = await this.#adapter.invoke<unknown>(url, {
590
+ method: 'GET',
591
+ signal,
592
+ telemetry: {
593
+ name: 'agentuity.email.getInbound',
594
+ attributes: {
595
+ id,
596
+ },
597
+ },
598
+ });
599
+ if (res.response.status === 404) {
600
+ return null;
601
+ }
602
+ if (res.ok) {
603
+ return unwrap<EmailInbound>(res.data, 'inbound');
604
+ }
605
+ throw await toServiceException('GET', url, res.response);
606
+ }
607
+
608
+ async listOutbound(addressId?: string): Promise<EmailOutbound[]> {
609
+ const queryParams = new URLSearchParams();
610
+ if (addressId) {
611
+ queryParams.set('address_id', addressId);
612
+ }
613
+ const queryString = queryParams.toString();
614
+ const url = buildUrl(
615
+ this.#baseUrl,
616
+ `/email/2025-03-17/outbound${queryString ? `?${queryString}` : ''}`
617
+ );
618
+ const signal = AbortSignal.timeout(30_000);
619
+ const res = await this.#adapter.invoke<unknown>(url, {
620
+ method: 'GET',
621
+ signal,
622
+ telemetry: {
623
+ name: 'agentuity.email.listOutbound',
624
+ attributes: {
625
+ ...(addressId && { addressId }),
626
+ },
627
+ },
628
+ });
629
+ if (res.response.status === 404) {
630
+ return [];
631
+ }
632
+ if (res.ok) {
633
+ const items = unwrap<unknown>(res.data, 'outbound');
634
+ return Array.isArray(items) ? (items as EmailOutbound[]) : [];
635
+ }
636
+ throw await toServiceException('GET', url, res.response);
637
+ }
638
+
639
+ async getOutbound(id: string): Promise<EmailOutbound | null> {
640
+ const url = buildUrl(
641
+ this.#baseUrl,
642
+ `/email/2025-03-17/outbound/${encodeURIComponent(id)}`
643
+ );
644
+ const signal = AbortSignal.timeout(30_000);
645
+ const res = await this.#adapter.invoke<unknown>(url, {
646
+ method: 'GET',
647
+ signal,
648
+ telemetry: {
649
+ name: 'agentuity.email.getOutbound',
650
+ attributes: {
651
+ id,
652
+ },
653
+ },
654
+ });
655
+ if (res.response.status === 404) {
656
+ return null;
657
+ }
658
+ if (res.ok) {
659
+ return unwrap<EmailOutbound>(res.data, 'outbound');
660
+ }
661
+ throw await toServiceException('GET', url, res.response);
662
+ }
663
+ }
@@ -4,7 +4,10 @@ export * from './exception.ts';
4
4
  export * from './keyvalue.ts';
5
5
  export * from './pagination.ts';
6
6
  export * from './sandbox.ts';
7
+ export * from './schedule.ts';
7
8
  export * from './session.ts';
8
9
  export * from './stream.ts';
10
+ export * from './task.ts';
9
11
  export * from './vector.ts';
12
+ export * from './email.ts';
10
13
  export { buildUrl, toServiceException, toPayload, fromResponse } from './_util.ts';