@agentuity/runtime 0.0.60 → 0.0.62
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/dist/_context.d.ts +11 -7
- package/dist/_context.d.ts.map +1 -1
- package/dist/_context.js +9 -2
- package/dist/_context.js.map +1 -1
- package/dist/_server.d.ts +4 -2
- package/dist/_server.d.ts.map +1 -1
- package/dist/_server.js +79 -31
- package/dist/_server.js.map +1 -1
- package/dist/_services.d.ts +1 -1
- package/dist/_services.d.ts.map +1 -1
- package/dist/_services.js +4 -2
- package/dist/_services.js.map +1 -1
- package/dist/_waituntil.d.ts.map +1 -1
- package/dist/_waituntil.js +5 -2
- package/dist/_waituntil.js.map +1 -1
- package/dist/agent.d.ts +647 -19
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +55 -6
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts +205 -28
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +181 -13
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +41 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/io/email.d.ts.map +1 -1
- package/dist/io/email.js +11 -3
- package/dist/io/email.js.map +1 -1
- package/dist/router.d.ts +282 -32
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +110 -35
- package/dist/router.js.map +1 -1
- package/dist/services/evalrun/http.d.ts.map +1 -1
- package/dist/services/evalrun/http.js +7 -5
- package/dist/services/evalrun/http.js.map +1 -1
- package/dist/services/local/_util.d.ts.map +1 -1
- package/dist/services/local/_util.js +3 -1
- package/dist/services/local/_util.js.map +1 -1
- package/dist/services/session/http.d.ts.map +1 -1
- package/dist/services/session/http.js +4 -3
- package/dist/services/session/http.js.map +1 -1
- package/dist/session.d.ts +308 -6
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +331 -23
- package/dist/session.js.map +1 -1
- package/package.json +8 -5
- package/src/_context.ts +37 -9
- package/src/_server.ts +96 -36
- package/src/_services.ts +9 -2
- package/src/_waituntil.ts +13 -2
- package/src/agent.ts +856 -68
- package/src/app.ts +238 -38
- package/src/index.ts +42 -2
- package/src/io/email.ts +23 -5
- package/src/router.ts +359 -83
- package/src/services/evalrun/http.ts +15 -4
- package/src/services/local/_util.ts +7 -1
- package/src/services/session/http.ts +5 -2
- package/src/session.ts +686 -26
package/src/app.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/** biome-ignore-all lint/suspicious/noExplicitAny: any are ok */
|
|
3
3
|
import { type Env as HonoEnv, Hono } from 'hono';
|
|
4
4
|
import type { cors } from 'hono/cors';
|
|
5
|
+
import type { BunWebSocketData } from 'hono/bun';
|
|
5
6
|
import type { Logger } from './logger';
|
|
6
7
|
import { createServer, getLogger } from './_server';
|
|
7
8
|
import type { Meter, Tracer } from '@opentelemetry/api';
|
|
@@ -26,7 +27,7 @@ export interface WorkbenchInstance {
|
|
|
26
27
|
|
|
27
28
|
type CorsOptions = Parameters<typeof cors>[0];
|
|
28
29
|
|
|
29
|
-
export interface AppConfig {
|
|
30
|
+
export interface AppConfig<TAppState = Record<string, never>> {
|
|
30
31
|
/**
|
|
31
32
|
* Override the default cors settings
|
|
32
33
|
*/
|
|
@@ -76,9 +77,19 @@ export interface AppConfig {
|
|
|
76
77
|
*/
|
|
77
78
|
workbench?: WorkbenchInstance;
|
|
78
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* Optional setup function called before server starts
|
|
82
|
+
* Returns app state that will be available in all agents and routes
|
|
83
|
+
*/
|
|
84
|
+
setup?: () => Promise<TAppState> | TAppState;
|
|
85
|
+
/**
|
|
86
|
+
* Optional shutdown function called when server is stopping
|
|
87
|
+
* Receives the app state returned from setup
|
|
88
|
+
*/
|
|
89
|
+
shutdown?: (state: TAppState) => Promise<void> | void;
|
|
79
90
|
}
|
|
80
91
|
|
|
81
|
-
export interface Variables {
|
|
92
|
+
export interface Variables<TAppState = Record<string, never>> {
|
|
82
93
|
logger: Logger;
|
|
83
94
|
meter: Meter;
|
|
84
95
|
tracer: Tracer;
|
|
@@ -91,6 +102,7 @@ export interface Variables {
|
|
|
91
102
|
objectstore: ObjectStorage;
|
|
92
103
|
stream: StreamStorage;
|
|
93
104
|
vector: VectorStorage;
|
|
105
|
+
app: TAppState;
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
export type TriggerType = SessionStartEvent['trigger'];
|
|
@@ -102,51 +114,128 @@ export interface PrivateVariables {
|
|
|
102
114
|
trigger: TriggerType;
|
|
103
115
|
}
|
|
104
116
|
|
|
105
|
-
export interface Env extends HonoEnv {
|
|
106
|
-
Variables: Variables
|
|
117
|
+
export interface Env<TAppState = Record<string, never>> extends HonoEnv {
|
|
118
|
+
Variables: Variables<TAppState>;
|
|
107
119
|
}
|
|
108
120
|
|
|
109
|
-
type AppEventMap = {
|
|
110
|
-
'agent.started': [
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
type AppEventMap<TAppState = Record<string, never>> = {
|
|
122
|
+
'agent.started': [
|
|
123
|
+
Agent<any, any, any, any, TAppState>,
|
|
124
|
+
AgentContext<any, any, any, any, TAppState>,
|
|
125
|
+
];
|
|
126
|
+
'agent.completed': [
|
|
127
|
+
Agent<any, any, any, any, TAppState>,
|
|
128
|
+
AgentContext<any, any, any, any, TAppState>,
|
|
129
|
+
];
|
|
130
|
+
'agent.errored': [
|
|
131
|
+
Agent<any, any, any, any, TAppState>,
|
|
132
|
+
AgentContext<any, any, any, any, TAppState>,
|
|
133
|
+
Error,
|
|
134
|
+
];
|
|
113
135
|
'session.started': [Session];
|
|
114
136
|
'session.completed': [Session];
|
|
115
137
|
'thread.created': [Thread];
|
|
116
138
|
'thread.destroyed': [Thread];
|
|
117
139
|
};
|
|
118
140
|
|
|
119
|
-
type AppEventCallback<K extends keyof AppEventMap
|
|
141
|
+
type AppEventCallback<K extends keyof AppEventMap<any>, TAppState = Record<string, never>> = (
|
|
120
142
|
eventName: K,
|
|
121
|
-
...args: AppEventMap[K]
|
|
143
|
+
...args: AppEventMap<TAppState>[K]
|
|
122
144
|
) => void | Promise<void>;
|
|
123
145
|
|
|
124
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Main application instance created by createApp().
|
|
148
|
+
* Provides access to the router, server, logger, and app state.
|
|
149
|
+
*
|
|
150
|
+
* @template TAppState - The type of application state returned from setup function
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const app = await createApp({
|
|
155
|
+
* setup: async () => ({ db: await connectDB() })
|
|
156
|
+
* });
|
|
157
|
+
*
|
|
158
|
+
* // Access state
|
|
159
|
+
* console.log(app.state.db);
|
|
160
|
+
*
|
|
161
|
+
* // Add routes
|
|
162
|
+
* app.router.get('/health', (c) => c.text('OK'));
|
|
163
|
+
*
|
|
164
|
+
* // Listen to events
|
|
165
|
+
* app.addEventListener('agent.started', (eventName, agent, ctx) => {
|
|
166
|
+
* console.log(`Agent ${agent.metadata.name} started`);
|
|
167
|
+
* });
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export class App<TAppState = Record<string, never>> {
|
|
125
171
|
/**
|
|
126
|
-
*
|
|
172
|
+
* The Hono router instance for defining routes.
|
|
127
173
|
*/
|
|
128
|
-
readonly router: Hono<Env
|
|
174
|
+
readonly router: Hono<Env<TAppState>>;
|
|
129
175
|
/**
|
|
130
|
-
*
|
|
176
|
+
* The Bun server instance.
|
|
131
177
|
*/
|
|
132
|
-
readonly server:
|
|
178
|
+
readonly server: Bun.Server<BunWebSocketData>;
|
|
133
179
|
/**
|
|
134
|
-
*
|
|
180
|
+
* The application logger instance.
|
|
135
181
|
*/
|
|
136
182
|
readonly logger: Logger;
|
|
183
|
+
/**
|
|
184
|
+
* The application state returned from the setup function.
|
|
185
|
+
* Available in all agents via ctx.app.
|
|
186
|
+
*/
|
|
187
|
+
readonly state: TAppState;
|
|
137
188
|
|
|
138
|
-
private eventListeners = new Map<
|
|
189
|
+
private eventListeners = new Map<
|
|
190
|
+
keyof AppEventMap<TAppState>,
|
|
191
|
+
Set<AppEventCallback<any, TAppState>>
|
|
192
|
+
>();
|
|
139
193
|
|
|
140
|
-
constructor(
|
|
141
|
-
|
|
142
|
-
|
|
194
|
+
constructor(
|
|
195
|
+
state: TAppState,
|
|
196
|
+
router: Hono<Env<TAppState>>,
|
|
197
|
+
server: Bun.Server<BunWebSocketData>
|
|
198
|
+
) {
|
|
199
|
+
this.state = state;
|
|
200
|
+
this.router = router;
|
|
201
|
+
this.server = server;
|
|
143
202
|
this.logger = getLogger() as Logger;
|
|
144
203
|
setGlobalApp(this);
|
|
145
204
|
}
|
|
146
205
|
|
|
147
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Register an event listener for application lifecycle events.
|
|
208
|
+
*
|
|
209
|
+
* Available events:
|
|
210
|
+
* - `agent.started` - Fired when an agent begins execution
|
|
211
|
+
* - `agent.completed` - Fired when an agent completes successfully
|
|
212
|
+
* - `agent.errored` - Fired when an agent throws an error
|
|
213
|
+
* - `session.started` - Fired when a new session starts
|
|
214
|
+
* - `session.completed` - Fired when a session completes
|
|
215
|
+
* - `thread.created` - Fired when a thread is created
|
|
216
|
+
* - `thread.destroyed` - Fired when a thread is destroyed
|
|
217
|
+
*
|
|
218
|
+
* @param eventName - The event name to listen for
|
|
219
|
+
* @param callback - The callback function to execute when the event fires
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* app.addEventListener('agent.started', (eventName, agent, ctx) => {
|
|
224
|
+
* console.log(`${agent.metadata.name} started for session ${ctx.sessionId}`);
|
|
225
|
+
* });
|
|
226
|
+
*
|
|
227
|
+
* app.addEventListener('agent.errored', (eventName, agent, ctx, error) => {
|
|
228
|
+
* console.error(`${agent.metadata.name} failed:`, error.message);
|
|
229
|
+
* });
|
|
230
|
+
*
|
|
231
|
+
* app.addEventListener('session.started', (eventName, session) => {
|
|
232
|
+
* console.log(`New session: ${session.id}`);
|
|
233
|
+
* });
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
addEventListener<K extends keyof AppEventMap<TAppState>>(
|
|
148
237
|
eventName: K,
|
|
149
|
-
callback: AppEventCallback<K>
|
|
238
|
+
callback: AppEventCallback<K, TAppState>
|
|
150
239
|
): void {
|
|
151
240
|
let callbacks = this.eventListeners.get(eventName);
|
|
152
241
|
if (!callbacks) {
|
|
@@ -156,18 +245,48 @@ export class App {
|
|
|
156
245
|
callbacks.add(callback);
|
|
157
246
|
}
|
|
158
247
|
|
|
159
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Remove a previously registered event listener.
|
|
250
|
+
*
|
|
251
|
+
* @param eventName - The event name to stop listening for
|
|
252
|
+
* @param callback - The callback function to remove
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* const handler = (eventName, agent, ctx) => {
|
|
257
|
+
* console.log('Agent started:', agent.metadata.name);
|
|
258
|
+
* };
|
|
259
|
+
*
|
|
260
|
+
* app.addEventListener('agent.started', handler);
|
|
261
|
+
* // Later...
|
|
262
|
+
* app.removeEventListener('agent.started', handler);
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
removeEventListener<K extends keyof AppEventMap<TAppState>>(
|
|
160
266
|
eventName: K,
|
|
161
|
-
callback: AppEventCallback<K>
|
|
267
|
+
callback: AppEventCallback<K, TAppState>
|
|
162
268
|
): void {
|
|
163
269
|
const callbacks = this.eventListeners.get(eventName);
|
|
164
270
|
if (!callbacks) return;
|
|
165
271
|
callbacks.delete(callback);
|
|
166
272
|
}
|
|
167
273
|
|
|
168
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Manually fire an application event.
|
|
276
|
+
* Typically used internally by the runtime, but can be used for custom events.
|
|
277
|
+
*
|
|
278
|
+
* @param eventName - The event name to fire
|
|
279
|
+
* @param args - The arguments to pass to event listeners
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```typescript
|
|
283
|
+
* // Fire a session completed event
|
|
284
|
+
* await app.fireEvent('session.completed', session);
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
async fireEvent<K extends keyof AppEventMap<TAppState>>(
|
|
169
288
|
eventName: K,
|
|
170
|
-
...args: AppEventMap[K]
|
|
289
|
+
...args: AppEventMap<TAppState>[K]
|
|
171
290
|
): Promise<void> {
|
|
172
291
|
const callbacks = this.eventListeners.get(eventName);
|
|
173
292
|
if (!callbacks || callbacks.size === 0) return;
|
|
@@ -178,34 +297,115 @@ export class App {
|
|
|
178
297
|
}
|
|
179
298
|
}
|
|
180
299
|
|
|
181
|
-
let globalApp: App | null = null;
|
|
300
|
+
let globalApp: App<any> | null = null;
|
|
182
301
|
|
|
183
|
-
function setGlobalApp(app: App): void {
|
|
302
|
+
function setGlobalApp(app: App<any>): void {
|
|
184
303
|
globalApp = app;
|
|
185
304
|
}
|
|
186
305
|
|
|
187
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Get the global app instance.
|
|
308
|
+
* Returns null if createApp() has not been called yet.
|
|
309
|
+
*
|
|
310
|
+
* @returns The global App instance or null
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```typescript
|
|
314
|
+
* const app = getApp();
|
|
315
|
+
* if (app) {
|
|
316
|
+
* console.log('Server running on port:', app.server.port);
|
|
317
|
+
* }
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
export function getApp(): App<any> | null {
|
|
188
321
|
return globalApp;
|
|
189
322
|
}
|
|
190
323
|
|
|
191
324
|
/**
|
|
192
|
-
*
|
|
325
|
+
* Creates a new Agentuity application with optional lifecycle hooks and service configuration.
|
|
326
|
+
*
|
|
327
|
+
* This is the main entry point for creating an Agentuity app. The app will:
|
|
328
|
+
* 1. Run the setup function (if provided) to initialize app state
|
|
329
|
+
* 2. Start the Bun server
|
|
330
|
+
* 3. Make the app state available in all agents via ctx.app
|
|
331
|
+
*
|
|
332
|
+
* @template TAppState - The type of application state returned from setup function
|
|
333
|
+
*
|
|
334
|
+
* @param config - Optional application configuration
|
|
335
|
+
* @param config.setup - Function to initialize app state, runs before server starts
|
|
336
|
+
* @param config.shutdown - Function to clean up resources when server stops
|
|
337
|
+
* @param config.cors - CORS configuration for HTTP routes
|
|
338
|
+
* @param config.services - Override default storage and service providers
|
|
339
|
+
*
|
|
340
|
+
* @returns Promise resolving to App instance with running server
|
|
193
341
|
*
|
|
194
|
-
* @
|
|
342
|
+
* @example
|
|
343
|
+
* ```typescript
|
|
344
|
+
* // Simple app with no state
|
|
345
|
+
* const app = await createApp();
|
|
346
|
+
*
|
|
347
|
+
* // App with database connection
|
|
348
|
+
* const app = await createApp({
|
|
349
|
+
* setup: async () => {
|
|
350
|
+
* const db = await connectDatabase();
|
|
351
|
+
* return { db };
|
|
352
|
+
* },
|
|
353
|
+
* shutdown: async (state) => {
|
|
354
|
+
* await state.db.close();
|
|
355
|
+
* }
|
|
356
|
+
* });
|
|
357
|
+
*
|
|
358
|
+
* // Access state in agents
|
|
359
|
+
* const agent = createAgent({
|
|
360
|
+
* handler: async (ctx, input) => {
|
|
361
|
+
* const db = ctx.app.db; // Strongly typed!
|
|
362
|
+
* return db.query('SELECT * FROM users');
|
|
363
|
+
* }
|
|
364
|
+
* });
|
|
365
|
+
*
|
|
366
|
+
* // App with custom services
|
|
367
|
+
* const app = await createApp({
|
|
368
|
+
* services: {
|
|
369
|
+
* useLocal: true, // Use local in-memory storage for development
|
|
370
|
+
* }
|
|
371
|
+
* });
|
|
372
|
+
* ```
|
|
195
373
|
*/
|
|
196
|
-
export function createApp
|
|
197
|
-
|
|
374
|
+
export async function createApp<TAppState = Record<string, never>>(
|
|
375
|
+
config?: AppConfig<TAppState>
|
|
376
|
+
): Promise<App<TAppState>> {
|
|
377
|
+
const initializer = async (): Promise<TAppState> => {
|
|
378
|
+
// Run setup if provided
|
|
379
|
+
if (config?.setup) {
|
|
380
|
+
return config.setup();
|
|
381
|
+
} else {
|
|
382
|
+
return {} as TAppState;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const router = new Hono<Env<TAppState>>();
|
|
387
|
+
const [server, state] = await createServer<TAppState>(router, initializer, config);
|
|
388
|
+
|
|
389
|
+
return new App(state, router, server);
|
|
198
390
|
}
|
|
199
391
|
|
|
200
392
|
/**
|
|
201
|
-
*
|
|
393
|
+
* Fire a global application event.
|
|
394
|
+
* Convenience function that calls fireEvent on the global app instance.
|
|
395
|
+
*
|
|
396
|
+
* @param eventName - The event name to fire
|
|
397
|
+
* @param args - The arguments to pass to event listeners
|
|
202
398
|
*
|
|
203
|
-
* @
|
|
204
|
-
*
|
|
399
|
+
* @example
|
|
400
|
+
* ```typescript
|
|
401
|
+
* // Fire from anywhere in your app
|
|
402
|
+
* await fireEvent('session.started', session);
|
|
403
|
+
* await fireEvent('agent.completed', agent, ctx);
|
|
404
|
+
* ```
|
|
205
405
|
*/
|
|
206
|
-
export async function fireEvent<K extends keyof AppEventMap
|
|
406
|
+
export async function fireEvent<K extends keyof AppEventMap<any>>(
|
|
207
407
|
eventName: K,
|
|
208
|
-
...args: AppEventMap[K]
|
|
408
|
+
...args: AppEventMap<any>[K]
|
|
209
409
|
) {
|
|
210
410
|
await globalApp?.fireEvent(eventName, ...args);
|
|
211
411
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,47 @@ export * from './eval';
|
|
|
6
6
|
export * from './session';
|
|
7
7
|
export * from './workbench';
|
|
8
8
|
export type { Logger } from './logger';
|
|
9
|
-
export { getRouter } from './_server';
|
|
9
|
+
export { getRouter, getAppState } from './_server';
|
|
10
10
|
export { Email, parseEmail } from './io/email';
|
|
11
11
|
export * from './services/evalrun';
|
|
12
|
-
export { getEvalRunEventProvider } from './_services';
|
|
12
|
+
export { getEvalRunEventProvider, getThreadProvider, getSessionProvider } from './_services';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Application state interface that gets automatically augmented based on your createApp setup function.
|
|
16
|
+
*
|
|
17
|
+
* This interface is empty by default but gets populated with strongly-typed properties
|
|
18
|
+
* when you define a setup function in createApp(). The Agentuity build tool automatically
|
|
19
|
+
* generates type augmentations in `.agentuity/.agentuity_runtime.ts`.
|
|
20
|
+
*
|
|
21
|
+
* **How it works:**
|
|
22
|
+
* 1. You define setup() in createApp() that returns an object
|
|
23
|
+
* 2. The build tool generates module augmentation for this interface
|
|
24
|
+
* 3. All agents get strongly-typed access to app state via `ctx.app`
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // In your app.ts:
|
|
29
|
+
* const app = await createApp({
|
|
30
|
+
* setup: async () => {
|
|
31
|
+
* const db = await connectDatabase();
|
|
32
|
+
* const redis = await connectRedis();
|
|
33
|
+
* return { db, redis };
|
|
34
|
+
* }
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* // In your agent:
|
|
38
|
+
* const agent = createAgent({
|
|
39
|
+
* handler: async (ctx, input) => {
|
|
40
|
+
* // ctx.app is strongly typed with { db, redis }!
|
|
41
|
+
* const user = await ctx.app.db.query('SELECT * FROM users');
|
|
42
|
+
* await ctx.app.redis.set('key', 'value');
|
|
43
|
+
* return user;
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* **Note:** If you're not seeing type hints for `ctx.app`, make sure you've run `bun run build`
|
|
49
|
+
* to generate the type augmentations.
|
|
50
|
+
*/
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
52
|
+
export interface AppState {}
|
package/src/io/email.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StructuredError } from '@agentuity/core';
|
|
1
2
|
import { type ParsedMail, type Headers, simpleParser, type AddressObject } from 'mailparser';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -143,6 +144,21 @@ export class Email {
|
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
const InvalidEmailEmptyError = StructuredError(
|
|
148
|
+
'InvalidEmailEmptyError',
|
|
149
|
+
'Failed to parse email: empty buffer'
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const InvalidEmailHeadersError = StructuredError(
|
|
153
|
+
'InvalidEmailHeadersError',
|
|
154
|
+
'Failed to parse email: missing headers'
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const InvalidEmailParseError = StructuredError(
|
|
158
|
+
'InvalidEmailParseError',
|
|
159
|
+
'Failed to parse email: body was invalid'
|
|
160
|
+
)<{ bufferSize: number; preview?: string }>();
|
|
161
|
+
|
|
146
162
|
/**
|
|
147
163
|
* Parse an email from a buffer and return an Email object.
|
|
148
164
|
*
|
|
@@ -152,22 +168,24 @@ export class Email {
|
|
|
152
168
|
*/
|
|
153
169
|
export async function parseEmail(data: Buffer): Promise<Email> {
|
|
154
170
|
if (data.length === 0) {
|
|
155
|
-
throw new
|
|
171
|
+
throw new InvalidEmailEmptyError();
|
|
156
172
|
}
|
|
157
173
|
|
|
158
174
|
const first16KB = data.slice(0, 16384).toString('utf-8', 0, Math.min(data.length, 16384));
|
|
159
175
|
const hasHeaders = /(^|\r?\n)[!-9;-~]+:\s/.test(first16KB);
|
|
160
176
|
|
|
161
177
|
if (!hasHeaders) {
|
|
162
|
-
throw new
|
|
178
|
+
throw new InvalidEmailHeadersError();
|
|
163
179
|
}
|
|
164
180
|
|
|
165
181
|
try {
|
|
166
182
|
const message = await simpleParser(data);
|
|
167
183
|
return new Email(message);
|
|
168
184
|
} catch (error) {
|
|
169
|
-
throw new
|
|
170
|
-
|
|
171
|
-
|
|
185
|
+
throw new InvalidEmailParseError({
|
|
186
|
+
bufferSize: data.length,
|
|
187
|
+
preview: data.subarray(0, 50).toString('hex'),
|
|
188
|
+
cause: error,
|
|
189
|
+
});
|
|
172
190
|
}
|
|
173
191
|
}
|