@agentuity/core 1.0.22 → 1.0.24

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