@agentuity/runtime 0.0.60 → 0.0.61

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.
Files changed (61) hide show
  1. package/dist/_context.d.ts +11 -7
  2. package/dist/_context.d.ts.map +1 -1
  3. package/dist/_context.js +9 -2
  4. package/dist/_context.js.map +1 -1
  5. package/dist/_server.d.ts +4 -2
  6. package/dist/_server.d.ts.map +1 -1
  7. package/dist/_server.js +71 -31
  8. package/dist/_server.js.map +1 -1
  9. package/dist/_services.d.ts +1 -1
  10. package/dist/_services.d.ts.map +1 -1
  11. package/dist/_services.js +4 -2
  12. package/dist/_services.js.map +1 -1
  13. package/dist/_waituntil.d.ts.map +1 -1
  14. package/dist/_waituntil.js +5 -2
  15. package/dist/_waituntil.js.map +1 -1
  16. package/dist/agent.d.ts +647 -19
  17. package/dist/agent.d.ts.map +1 -1
  18. package/dist/agent.js +55 -6
  19. package/dist/agent.js.map +1 -1
  20. package/dist/app.d.ts +205 -28
  21. package/dist/app.d.ts.map +1 -1
  22. package/dist/app.js +181 -13
  23. package/dist/app.js.map +1 -1
  24. package/dist/index.d.ts +41 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/io/email.d.ts.map +1 -1
  29. package/dist/io/email.js +11 -3
  30. package/dist/io/email.js.map +1 -1
  31. package/dist/router.d.ts +282 -32
  32. package/dist/router.d.ts.map +1 -1
  33. package/dist/router.js +110 -35
  34. package/dist/router.js.map +1 -1
  35. package/dist/services/evalrun/http.d.ts.map +1 -1
  36. package/dist/services/evalrun/http.js +7 -5
  37. package/dist/services/evalrun/http.js.map +1 -1
  38. package/dist/services/local/_util.d.ts.map +1 -1
  39. package/dist/services/local/_util.js +3 -1
  40. package/dist/services/local/_util.js.map +1 -1
  41. package/dist/services/session/http.d.ts.map +1 -1
  42. package/dist/services/session/http.js +4 -3
  43. package/dist/services/session/http.js.map +1 -1
  44. package/dist/session.d.ts +284 -4
  45. package/dist/session.d.ts.map +1 -1
  46. package/dist/session.js +2 -2
  47. package/dist/session.js.map +1 -1
  48. package/package.json +5 -4
  49. package/src/_context.ts +37 -9
  50. package/src/_server.ts +88 -36
  51. package/src/_services.ts +9 -2
  52. package/src/_waituntil.ts +13 -2
  53. package/src/agent.ts +856 -68
  54. package/src/app.ts +238 -38
  55. package/src/index.ts +42 -2
  56. package/src/io/email.ts +23 -5
  57. package/src/router.ts +359 -83
  58. package/src/services/evalrun/http.ts +15 -4
  59. package/src/services/local/_util.ts +7 -1
  60. package/src/services/session/http.ts +5 -2
  61. package/src/session.ts +297 -4
package/src/_server.ts CHANGED
@@ -45,6 +45,8 @@ let globalRouterInstance: Hono<Env> | null = null;
45
45
 
46
46
  let globalLogger: Logger | null = null;
47
47
  let globalTracer: Tracer | null = null;
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ let globalAppState: any = null;
48
50
 
49
51
  export function getServer() {
50
52
  return globalServerInstance;
@@ -64,6 +66,10 @@ export function getTracer() {
64
66
  return globalTracer;
65
67
  }
66
68
 
69
+ export function getAppState() {
70
+ return globalAppState;
71
+ }
72
+
67
73
  function isDevelopment(): boolean {
68
74
  const devmode = runtimeConfig.isDevMode();
69
75
  const environment = runtimeConfig.getEnvironment();
@@ -131,11 +137,27 @@ export function privateContext<E extends Env>(c: HonoContext<E>) {
131
137
  return c as unknown as HonoContext<{ Variables: PrivateVariables }>;
132
138
  }
133
139
 
134
- export const createServer = <E extends Env>(router: Hono<E>, config?: AppConfig) => {
140
+ let startupPromise: Promise<void> | undefined;
141
+ let startupPromiseResolver: (() => void) | undefined;
142
+ let isShutdown = false;
143
+
144
+ export const notifyReady = () => {
145
+ startupPromiseResolver?.();
146
+ };
147
+
148
+ export const createServer = async <TAppState>(
149
+ router: Hono<Env<TAppState>>,
150
+ appStateInitializer: () => Promise<TAppState>,
151
+ config?: AppConfig<TAppState>
152
+ ): Promise<[Bun.Server<BunWebSocketData>, TAppState]> => {
135
153
  if (globalServerInstance) {
136
- return globalServerInstance;
154
+ return [globalServerInstance, globalAppState as TAppState];
137
155
  }
138
156
 
157
+ startupPromise = new Promise((resolve) => {
158
+ startupPromiseResolver = resolve;
159
+ });
160
+
139
161
  runtimeConfig.init();
140
162
 
141
163
  const logLevel = process.env.AGENTUITY_LOG_LEVEL || 'info';
@@ -153,22 +175,13 @@ export const createServer = <E extends Env>(router: Hono<E>, config?: AppConfig)
153
175
  // Create services (may return local router)
154
176
  const servicesResult = createServices(otel.logger, config, serverUrl);
155
177
 
156
- const server = Bun.serve({
157
- hostname,
158
- development: isDevelopment(),
159
- fetch: router.fetch,
160
- idleTimeout: 0,
161
- port,
162
- websocket,
163
- });
178
+ // Create the App State
179
+ globalAppState = await appStateInitializer();
164
180
 
165
181
  globalRouterInstance = router as unknown as Hono<Env>;
166
- globalServerInstance = server;
167
182
  globalLogger = otel.logger;
168
183
  globalTracer = otel.tracer;
169
184
 
170
- let isShutdown = false;
171
-
172
185
  router.onError((error, _c) => {
173
186
  if (error instanceof HTTPException) {
174
187
  otel.logger.error('HTTP Error: %s (%d)', error.cause, error.status);
@@ -178,42 +191,45 @@ export const createServer = <E extends Env>(router: Hono<E>, config?: AppConfig)
178
191
  otel.logger.error('Unauthenticated Error: %s', error.message);
179
192
  return new Response(error.message, { status: 501 });
180
193
  }
181
- if (
182
- error instanceof ServiceException ||
183
- ('statusCode' in error && typeof error.statusCode === 'number')
184
- ) {
185
- const serviceError = error as ServiceException;
194
+ if (error instanceof ServiceException) {
195
+ const serviceError = error as InstanceType<typeof ServiceException>;
186
196
  otel.logger.error(
187
- 'Service Exception: %s (%s returned HTTP status code: %d)',
197
+ 'Service Exception: %s (%s returned HTTP status code: %d%s)',
188
198
  error.message,
189
199
  serviceError.url,
190
- serviceError.statusCode
200
+ serviceError.statusCode,
201
+ serviceError.sessionId ? `, session: ${serviceError.sessionId}` : ''
191
202
  );
192
203
  return new Response(error.message, {
193
204
  status: serviceError.statusCode ?? 500,
194
205
  });
195
206
  }
196
- otel.logger.error('Unhandled Server Error: %s', JSON.stringify(error));
207
+ otel.logger.error('Unhandled Server Error: %s', error);
197
208
  return new Response('Internal Server Error', { status: 500 });
198
209
  });
199
210
 
200
- const threadProvider = getThreadProvider();
201
- const sessionProvider = getSessionProvider();
211
+ const blockOnStartup = async () => {
212
+ // block until completing the setup if still running
213
+ if (startupPromise) {
214
+ await startupPromise;
215
+ startupPromise = undefined;
216
+ startupPromiseResolver = undefined;
217
+ }
218
+ };
202
219
 
203
- let initPromise: Promise<void> | undefined = new Promise((resolve, reject) => {
204
- Promise.all([threadProvider.initialize(), sessionProvider.initialize()])
205
- .then(() => resolve())
206
- .catch(reject);
220
+ router.get('/_health', async (c) => {
221
+ await blockOnStartup();
222
+ return c.text('OK');
207
223
  });
208
224
 
209
225
  router.use(async (c, next) => {
210
- if (initPromise) {
211
- await initPromise;
212
- initPromise = undefined;
213
- }
226
+ await blockOnStartup();
227
+
214
228
  c.set('logger', otel.logger);
215
229
  c.set('tracer', otel.tracer);
216
230
  c.set('meter', otel.meter);
231
+ c.set('app', globalAppState);
232
+
217
233
  const isWebSocket = c.req.header('upgrade')?.toLowerCase() === 'websocket';
218
234
  const skipLogging = c.req.path.startsWith('/_agentuity/');
219
235
  const started = performance.now();
@@ -255,7 +271,6 @@ export const createServer = <E extends Env>(router: Hono<E>, config?: AppConfig)
255
271
  })
256
272
  );
257
273
 
258
- router.get('/_health', (c) => c.text('OK'));
259
274
  router.route('/_agentuity', createAgentuityAPIs());
260
275
 
261
276
  // Mount local storage router if using local services
@@ -275,7 +290,7 @@ export const createServer = <E extends Env>(router: Hono<E>, config?: AppConfig)
275
290
  // in production there is no .agentuity folder
276
291
  routeMappingPath = join(projectRoot, '.routemapping.json');
277
292
  } else {
278
- routeMappingPath = join(projectRoot, '.agentuity', '.routemapping.json');
293
+ routeMappingPath = join(import.meta.dirname, '..', '.routemapping.json');
279
294
  }
280
295
  const file = Bun.file(routeMappingPath);
281
296
  if (!(await file.exists())) {
@@ -337,7 +352,33 @@ export const createServer = <E extends Env>(router: Hono<E>, config?: AppConfig)
337
352
  process.exit(1);
338
353
  }, 5_000);
339
354
  try {
340
- await server.stop();
355
+ // stop accepting new connections
356
+ if (globalServerInstance) {
357
+ await globalServerInstance.stop();
358
+ }
359
+
360
+ // wait for idle
361
+ const shutdownStarted = Date.now();
362
+ otel.logger.debug('waiting for pending connections to complete');
363
+ while (Date.now() - shutdownStarted < 60_000 * 2) {
364
+ if ((globalServerInstance?.pendingRequests ?? 0) > 0) {
365
+ await Bun.sleep(1_000);
366
+ } else {
367
+ break;
368
+ }
369
+ }
370
+ otel.logger.debug('no more pending connections');
371
+
372
+ // Run agent shutdowns first
373
+ const { runAgentShutdowns } = await import('./agent');
374
+ await runAgentShutdowns(globalAppState);
375
+
376
+ // Run app shutdown if provided
377
+ if (config?.shutdown && globalAppState) {
378
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
379
+ await config.shutdown(globalAppState as any);
380
+ }
381
+
341
382
  await otel.shutdown();
342
383
  otel.logger.debug('shutdown completed');
343
384
  } finally {
@@ -373,13 +414,24 @@ export const createServer = <E extends Env>(router: Hono<E>, config?: AppConfig)
373
414
  process.exit(1);
374
415
  });
375
416
 
376
- return server;
417
+ const server = Bun.serve({
418
+ hostname,
419
+ development: isDevelopment(),
420
+ fetch: router.fetch,
421
+ idleTimeout: 0,
422
+ port,
423
+ websocket,
424
+ id: null,
425
+ });
426
+ globalServerInstance = server;
427
+
428
+ return [server, globalAppState];
377
429
  };
378
430
 
379
431
  const createAgentuityAPIs = () => {
380
432
  const router = new Hono<Env>();
381
433
  router.get('idle', (c) => {
382
- if (isIdle()) {
434
+ if (isIdle() || !isShutdown) {
383
435
  return c.text('OK', { status: 200 });
384
436
  }
385
437
  return c.text('NO', { status: 200 });
package/src/_services.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  type Logger,
16
16
  type SessionEventProvider,
17
17
  type EvalRunEventProvider,
18
+ StructuredError,
18
19
  } from '@agentuity/core';
19
20
  import { APIClient, createServerFetchAdapter, getServiceUrls } from '@agentuity/server';
20
21
  import {
@@ -189,7 +190,13 @@ let evalRunEvent: EvalRunEventProvider;
189
190
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
191
  let localRouter: any | null = null;
191
192
 
192
- export function createServices(logger: Logger, config?: AppConfig, serverUrl?: string) {
193
+ const ServerUrlMissingError = StructuredError(
194
+ 'ServerUrlMissingError',
195
+ 'serverUrl is required when using local services'
196
+ );
197
+
198
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
+ export function createServices(logger: Logger, config?: AppConfig<any>, serverUrl?: string) {
193
200
  const authenticated = isAuthenticated();
194
201
  const useLocal = config?.services?.useLocal ?? false;
195
202
  adapter = createFetchAdapter(logger);
@@ -203,7 +210,7 @@ export function createServices(logger: Logger, config?: AppConfig, serverUrl?: s
203
210
  const projectPath = normalizeProjectPath();
204
211
 
205
212
  if (!serverUrl) {
206
- throw new Error('serverUrl is required when using local services');
213
+ throw new ServerUrlMissingError();
207
214
  }
208
215
 
209
216
  logger.info('Using local services (development only)');
package/src/_waituntil.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { context, SpanStatusCode, type Tracer, trace } from '@opentelemetry/api';
2
2
  import type { Logger } from './logger';
3
3
  import { internal } from './logger/internal';
4
+ import { StructuredError } from '@agentuity/core';
4
5
 
5
6
  let running = 0;
6
7
 
@@ -13,6 +14,16 @@ export function hasWaitUntilPending(): boolean {
13
14
  return running > 0;
14
15
  }
15
16
 
17
+ const WaitUntilInvalidStateError = StructuredError(
18
+ 'WaitUntilInvalidStateError',
19
+ 'waitUntil cannot be called after waitUntilAll has been called'
20
+ );
21
+
22
+ const WaitUntilAllInvalidStateError = StructuredError(
23
+ 'WaitUntilAllInvalidStateError',
24
+ 'waitUntilAll can only be called once per instance'
25
+ );
26
+
16
27
  export default class WaitUntilHandler {
17
28
  private promises: Promise<void>[];
18
29
  private tracer: Tracer;
@@ -26,7 +37,7 @@ export default class WaitUntilHandler {
26
37
 
27
38
  public waitUntil(promise: Promise<void> | (() => void | Promise<void>)): void {
28
39
  if (this.hasCalledWaitUntilAll) {
29
- throw new Error('Cannot call waitUntil after waitUntilAll has been called');
40
+ throw new WaitUntilInvalidStateError();
30
41
  }
31
42
  running++;
32
43
  internal.debug('wait until called, running: %d', running);
@@ -69,7 +80,7 @@ export default class WaitUntilHandler {
69
80
  internal.debug(`🔍 waitUntilAll() called for session ${sessionId} (count: %d)`, running);
70
81
 
71
82
  if (this.hasCalledWaitUntilAll) {
72
- throw new Error('waitUntilAll can only be called once per instance');
83
+ throw new WaitUntilAllInvalidStateError();
73
84
  }
74
85
  this.hasCalledWaitUntilAll = true;
75
86