@agentuity/runtime 0.0.43 → 0.0.45

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 (130) hide show
  1. package/AGENTS.md +11 -9
  2. package/README.md +4 -4
  3. package/dist/_context.d.ts +12 -4
  4. package/dist/_context.d.ts.map +1 -1
  5. package/dist/_server.d.ts +7 -4
  6. package/dist/_server.d.ts.map +1 -1
  7. package/dist/_services.d.ts +13 -2
  8. package/dist/_services.d.ts.map +1 -1
  9. package/dist/_util.d.ts +1 -1
  10. package/dist/_util.d.ts.map +1 -1
  11. package/dist/_waituntil.d.ts +1 -3
  12. package/dist/_waituntil.d.ts.map +1 -1
  13. package/dist/agent.d.ts +41 -14
  14. package/dist/agent.d.ts.map +1 -1
  15. package/dist/app.d.ts +90 -8
  16. package/dist/app.d.ts.map +1 -1
  17. package/dist/eval.d.ts +79 -0
  18. package/dist/eval.d.ts.map +1 -0
  19. package/dist/index.d.ts +6 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/io/email.d.ts +77 -0
  22. package/dist/io/email.d.ts.map +1 -0
  23. package/dist/logger/console.d.ts +7 -1
  24. package/dist/logger/console.d.ts.map +1 -1
  25. package/dist/logger/user.d.ts.map +1 -1
  26. package/dist/otel/config.d.ts +3 -1
  27. package/dist/otel/config.d.ts.map +1 -1
  28. package/dist/otel/console.d.ts +2 -1
  29. package/dist/otel/console.d.ts.map +1 -1
  30. package/dist/otel/exporters/index.d.ts +4 -0
  31. package/dist/otel/exporters/index.d.ts.map +1 -0
  32. package/dist/otel/exporters/jsonl-log-exporter.d.ts +36 -0
  33. package/dist/otel/exporters/jsonl-log-exporter.d.ts.map +1 -0
  34. package/dist/otel/exporters/jsonl-metric-exporter.d.ts +40 -0
  35. package/dist/otel/exporters/jsonl-metric-exporter.d.ts.map +1 -0
  36. package/dist/otel/exporters/jsonl-trace-exporter.d.ts +36 -0
  37. package/dist/otel/exporters/jsonl-trace-exporter.d.ts.map +1 -0
  38. package/dist/otel/http.d.ts.map +1 -1
  39. package/dist/otel/logger.d.ts +8 -6
  40. package/dist/otel/logger.d.ts.map +1 -1
  41. package/dist/otel/otel.d.ts +8 -2
  42. package/dist/otel/otel.d.ts.map +1 -1
  43. package/dist/router.d.ts +4 -1
  44. package/dist/router.d.ts.map +1 -1
  45. package/dist/services/evalrun/composite.d.ts +21 -0
  46. package/dist/services/evalrun/composite.d.ts.map +1 -0
  47. package/dist/services/evalrun/http.d.ts +24 -0
  48. package/dist/services/evalrun/http.d.ts.map +1 -0
  49. package/dist/services/evalrun/index.d.ts +5 -0
  50. package/dist/services/evalrun/index.d.ts.map +1 -0
  51. package/dist/services/evalrun/json.d.ts +21 -0
  52. package/dist/services/evalrun/json.d.ts.map +1 -0
  53. package/dist/services/evalrun/local.d.ts +19 -0
  54. package/dist/services/evalrun/local.d.ts.map +1 -0
  55. package/dist/services/local/_db.d.ts +4 -0
  56. package/dist/services/local/_db.d.ts.map +1 -0
  57. package/dist/services/local/_router.d.ts +3 -0
  58. package/dist/services/local/_router.d.ts.map +1 -0
  59. package/dist/services/local/_util.d.ts +18 -0
  60. package/dist/services/local/_util.d.ts.map +1 -0
  61. package/dist/services/local/index.d.ts +8 -0
  62. package/dist/services/local/index.d.ts.map +1 -0
  63. package/dist/services/local/keyvalue.d.ts +10 -0
  64. package/dist/services/local/keyvalue.d.ts.map +1 -0
  65. package/dist/services/local/objectstore.d.ts +11 -0
  66. package/dist/services/local/objectstore.d.ts.map +1 -0
  67. package/dist/services/local/stream.d.ts +10 -0
  68. package/dist/services/local/stream.d.ts.map +1 -0
  69. package/dist/services/local/vector.d.ts +13 -0
  70. package/dist/services/local/vector.d.ts.map +1 -0
  71. package/dist/services/session/composite.d.ts +21 -0
  72. package/dist/services/session/composite.d.ts.map +1 -0
  73. package/dist/services/session/http.d.ts +23 -0
  74. package/dist/services/session/http.d.ts.map +1 -0
  75. package/dist/services/session/index.d.ts +5 -0
  76. package/dist/services/session/index.d.ts.map +1 -0
  77. package/dist/services/session/json.d.ts +22 -0
  78. package/dist/services/session/json.d.ts.map +1 -0
  79. package/dist/services/session/local.d.ts +19 -0
  80. package/dist/services/session/local.d.ts.map +1 -0
  81. package/dist/session.d.ts +70 -0
  82. package/dist/session.d.ts.map +1 -0
  83. package/package.json +10 -6
  84. package/src/_config.ts +1 -1
  85. package/src/_context.ts +19 -16
  86. package/src/_server.ts +284 -42
  87. package/src/_services.ts +147 -34
  88. package/src/_util.ts +2 -3
  89. package/src/_waituntil.ts +5 -153
  90. package/src/agent.ts +667 -65
  91. package/src/app.ts +159 -13
  92. package/src/eval.ts +95 -0
  93. package/src/index.ts +6 -1
  94. package/src/io/email.ts +173 -0
  95. package/src/logger/console.ts +196 -17
  96. package/src/logger/user.ts +7 -3
  97. package/src/otel/config.ts +7 -44
  98. package/src/otel/console.ts +8 -4
  99. package/src/otel/exporters/README.md +217 -0
  100. package/src/otel/exporters/index.ts +3 -0
  101. package/src/otel/exporters/jsonl-log-exporter.ts +113 -0
  102. package/src/otel/exporters/jsonl-metric-exporter.ts +120 -0
  103. package/src/otel/exporters/jsonl-trace-exporter.ts +121 -0
  104. package/src/otel/http.ts +3 -1
  105. package/src/otel/logger.ts +87 -37
  106. package/src/otel/otel.ts +43 -22
  107. package/src/router.ts +44 -4
  108. package/src/services/evalrun/composite.ts +34 -0
  109. package/src/services/evalrun/http.ts +112 -0
  110. package/src/services/evalrun/index.ts +4 -0
  111. package/src/services/evalrun/json.ts +46 -0
  112. package/src/services/evalrun/local.ts +28 -0
  113. package/src/services/local/README.md +1576 -0
  114. package/src/services/local/_db.ts +182 -0
  115. package/src/services/local/_router.ts +86 -0
  116. package/src/services/local/_util.ts +49 -0
  117. package/src/services/local/index.ts +7 -0
  118. package/src/services/local/keyvalue.ts +118 -0
  119. package/src/services/local/objectstore.ts +152 -0
  120. package/src/services/local/stream.ts +296 -0
  121. package/src/services/local/vector.ts +264 -0
  122. package/src/services/session/composite.ts +33 -0
  123. package/src/services/session/http.ts +64 -0
  124. package/src/services/session/index.ts +4 -0
  125. package/src/services/session/json.ts +42 -0
  126. package/src/services/session/local.ts +28 -0
  127. package/src/session.ts +284 -0
  128. package/dist/_unauthenticated.d.ts +0 -26
  129. package/dist/_unauthenticated.d.ts.map +0 -1
  130. package/src/_unauthenticated.ts +0 -126
package/src/_server.ts CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
1
  import {
3
2
  context,
4
3
  SpanKind,
@@ -7,29 +6,39 @@ import {
7
6
  type Tracer,
8
7
  trace,
9
8
  type Attributes,
9
+ propagation,
10
10
  } from '@opentelemetry/api';
11
+ import { TraceState } from '@opentelemetry/core';
11
12
  import type { Span } from '@opentelemetry/sdk-trace-base';
12
13
  import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
13
- import { ServiceException } from '@agentuity/core';
14
+ import { type LogLevel, ServiceException } from '@agentuity/core';
14
15
  import { cors } from 'hono/cors';
15
16
  import { createMiddleware } from 'hono/factory';
16
- import { Hono } from 'hono';
17
+ import { Hono, type Context as HonoContext } from 'hono';
17
18
  import { HTTPException } from 'hono/http-exception';
18
19
  import type { BunWebSocketData } from 'hono/bun';
20
+ import { matchedRoutes } from 'hono/route';
19
21
  import { websocket } from 'hono/bun';
20
- import type { AppConfig, Env } from './app';
22
+ import { join } from 'node:path';
23
+ import type { AppConfig, Env, PrivateVariables } from './app';
21
24
  import { extractTraceContextFromRequest } from './otel/http';
22
25
  import { register } from './otel/config';
23
26
  import type { Logger } from './logger';
24
27
  import { isIdle } from './_idle';
25
28
  import * as runtimeConfig from './_config';
26
29
  import { inAgentContext, getAgentContext } from './_context';
27
- import { createServices } from './_services';
30
+ import {
31
+ createServices,
32
+ getThreadProvider,
33
+ getSessionProvider,
34
+ getSessionEventProvider,
35
+ } from './_services';
36
+ import { generateId } from './session';
37
+ import WaitUntilHandler from './_waituntil';
28
38
 
29
39
  let globalServerInstance: Bun.Server<BunWebSocketData> | null = null;
30
40
 
31
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
- let globalAppInstance: Hono<any> | null = null;
41
+ let globalRouterInstance: Hono<Env> | null = null;
33
42
 
34
43
  let globalLogger: Logger | null = null;
35
44
  let globalTracer: Tracer | null = null;
@@ -38,8 +47,8 @@ export function getServer() {
38
47
  return globalServerInstance;
39
48
  }
40
49
 
41
- export function getApp() {
42
- return globalAppInstance;
50
+ export function getRouter() {
51
+ return globalRouterInstance;
43
52
  }
44
53
 
45
54
  export function getLogger() {
@@ -57,7 +66,7 @@ function isDevelopment(): boolean {
57
66
  }
58
67
 
59
68
  function getPort(): number {
60
- return Number.parseInt(process.env.AGENTUITY_PORT ?? process.env.PORT ?? '3000') || 3000;
69
+ return Number.parseInt(process.env.AGENTUITY_PORT ?? process.env.PORT ?? '3500', 10) || 3500;
61
70
  }
62
71
 
63
72
  const spanProcessors: SpanProcessor[] = [];
@@ -111,33 +120,46 @@ function registerAgentuitySpanProcessor() {
111
120
  addSpanProcessor(new RegisterAgentSpanProcessor());
112
121
  }
113
122
 
114
- export const createServer = <E extends Env>(app: Hono<E>, config?: AppConfig) => {
123
+ export function privateContext<E extends Env>(c: HonoContext<E>) {
124
+ return c as unknown as HonoContext<{ Variables: PrivateVariables }>;
125
+ }
126
+
127
+ export const createServer = <E extends Env>(router: Hono<E>, config?: AppConfig) => {
115
128
  if (globalServerInstance) {
116
129
  return globalServerInstance;
117
130
  }
118
131
 
119
- createServices(config);
132
+ const logLevel = process.env.AGENTUITY_LOG_LEVEL || 'info';
133
+ const port = getPort();
134
+ const hostname = '127.0.0.1';
135
+ const serverUrl = `http://${hostname}:${port}`;
136
+
137
+ // this must come before registering any otel stuff
120
138
  registerAgentuitySpanProcessor();
121
139
 
140
+ // Create the telemetry and logger
141
+ const otel = register({ processors: spanProcessors, logLevel: logLevel as LogLevel });
142
+
143
+ // Create services (may return local router)
144
+ const servicesResult = createServices(otel.logger, config, serverUrl);
145
+
122
146
  const server = Bun.serve({
123
- hostname: '127.0.0.1',
147
+ hostname,
124
148
  development: isDevelopment(),
125
- fetch: app.fetch,
149
+ fetch: router.fetch,
126
150
  idleTimeout: 0,
127
- port: getPort(),
151
+ port,
128
152
  websocket,
129
153
  });
130
154
 
131
- const otel = register({ processors: spanProcessors });
132
-
133
- globalAppInstance = app;
155
+ globalRouterInstance = router as unknown as Hono<Env>;
134
156
  globalServerInstance = server;
135
157
  globalLogger = otel.logger;
136
158
  globalTracer = otel.tracer;
137
159
 
138
160
  let isShutdown = false;
139
161
 
140
- app.onError((error, _c) => {
162
+ router.onError((error, _c) => {
141
163
  if (error instanceof HTTPException) {
142
164
  otel.logger.error('HTTP Error: %s (%d)', error.cause, error.status);
143
165
  return error.getResponse();
@@ -150,16 +172,35 @@ export const createServer = <E extends Env>(app: Hono<E>, config?: AppConfig) =>
150
172
  error instanceof ServiceException ||
151
173
  ('statusCode' in error && typeof error.statusCode === 'number')
152
174
  ) {
153
- otel.logger.error('Service Exception: %s (%d)', error.message, error.statusCode);
175
+ const serviceError = error as ServiceException;
176
+ otel.logger.error(
177
+ 'Service Exception: %s (%s returned HTTP status code: %d)',
178
+ error.message,
179
+ serviceError.url,
180
+ serviceError.statusCode
181
+ );
154
182
  return new Response(error.message, {
155
- status: (error.statusCode as number) ?? 500,
183
+ status: serviceError.statusCode ?? 500,
156
184
  });
157
185
  }
158
- otel.logger.error('Unhandled Server Error: %s', error);
186
+ otel.logger.error('Unhandled Server Error: %s', JSON.stringify(error));
159
187
  return new Response('Internal Server Error', { status: 500 });
160
188
  });
161
189
 
162
- app.use(async (c, next) => {
190
+ const threadProvider = getThreadProvider();
191
+ const sessionProvider = getSessionProvider();
192
+
193
+ let initPromise: Promise<void> | undefined = new Promise((resolve, reject) => {
194
+ Promise.all([threadProvider.initialize(), sessionProvider.initialize()])
195
+ .then(() => resolve())
196
+ .catch(reject);
197
+ });
198
+
199
+ router.use(async (c, next) => {
200
+ if (initPromise) {
201
+ await initPromise;
202
+ initPromise = undefined;
203
+ }
163
204
  c.set('logger', otel.logger);
164
205
  c.set('tracer', otel.tracer);
165
206
  c.set('meter', otel.meter);
@@ -169,7 +210,9 @@ export const createServer = <E extends Env>(app: Hono<E>, config?: AppConfig) =>
169
210
  if (!skipLogging) {
170
211
  otel.logger.debug('%s %s started', c.req.method, c.req.path);
171
212
  }
213
+
172
214
  await next();
215
+
173
216
  // Don't log completion for websocket upgrades - they stay open
174
217
  if (!skipLogging && !isWebSocket) {
175
218
  otel.logger.debug(
@@ -183,7 +226,7 @@ export const createServer = <E extends Env>(app: Hono<E>, config?: AppConfig) =>
183
226
  });
184
227
 
185
228
  // setup the cors middleware
186
- app.use(
229
+ router.use(
187
230
  '*',
188
231
  cors({
189
232
  origin: config?.cors?.origin ?? ((origin) => origin),
@@ -202,23 +245,78 @@ export const createServer = <E extends Env>(app: Hono<E>, config?: AppConfig) =>
202
245
  })
203
246
  );
204
247
 
205
- app.route('/_agentuity', createAgentuityAPIs());
248
+ router.get('/_health', (c) => c.text('OK'));
249
+ router.route('/_agentuity', createAgentuityAPIs());
206
250
 
207
- // Attach services to context for API routes
208
- app.use('/api/*', async (c, next) => {
209
- const { registerServices } = await import('./_services');
210
- registerServices(c);
211
- await next();
251
+ // Mount local storage router if using local services
252
+ if (servicesResult?.localRouter) {
253
+ router.route('/', servicesResult.localRouter);
254
+ }
255
+
256
+ // we create a middleware that attempts to match our routeid to the incoming route
257
+ let routeMapping: Record<string, string>;
258
+ const routePathMapper = createMiddleware<Env>(async (c, next) => {
259
+ if (!routeMapping) {
260
+ // Look for .routemapping.json in the project's .agentuity directory
261
+ // This is where the build plugin writes it (build.config.outdir)
262
+ const projectRoot = process.cwd();
263
+ const routeMappingPath = join(projectRoot, '.agentuity', '.routemapping.json');
264
+ const file = Bun.file(routeMappingPath);
265
+ if (!(await file.exists())) {
266
+ c.var.logger.fatal(
267
+ 'error loading the .routemapping.json from the %s directory. this is a build issue!',
268
+ routeMappingPath
269
+ );
270
+ }
271
+ routeMapping = (await file.json()) as Record<string, string>;
272
+ }
273
+ const matches = matchedRoutes(c).filter(
274
+ (m) => m.method !== 'ALL' && (m.path.startsWith('/api') || m.path.startsWith('/agent/'))
275
+ );
276
+ const _c = privateContext(c);
277
+ if (matches.length > 0) {
278
+ const method = c.req.method.toLowerCase();
279
+ for (const m of matches) {
280
+ const found = routeMapping[`${method} ${m.path}`];
281
+ if (found) {
282
+ _c.set('routeId', found);
283
+ break;
284
+ }
285
+ }
286
+ }
287
+ _c.set('trigger', 'api'); // will get overwritten below if another trigger
288
+ return next();
289
+ });
290
+
291
+ router.use('/agent/*', routePathMapper);
292
+ router.use('/api/*', routePathMapper);
293
+
294
+ // Attach services and agent registry to context for API routes
295
+ router.use('/api/*', async (c, next) => {
296
+ const { createAgentMiddleware } = await import('./agent');
297
+ // Use a null agent name to just populate the agent registry without setting current agent
298
+ return createAgentMiddleware('')(c, next);
212
299
  });
213
300
 
214
- app.use('/api/*', otelMiddleware);
215
- app.use('/agent/*', otelMiddleware);
301
+ // set the trigger for specific types
302
+ for (const trigger of ['sms', 'email', 'cron'] as const) {
303
+ const middleware = createMiddleware(async (c, next) => {
304
+ const _c = privateContext(c);
305
+ _c.set('trigger', trigger);
306
+ await next();
307
+ });
308
+ router.use(`/api/${trigger}/*`, middleware);
309
+ router.use(`/agent/${trigger}/*`, middleware);
310
+ }
311
+
312
+ router.use('/api/*', otelMiddleware);
313
+ router.use('/agent/*', otelMiddleware);
216
314
 
217
315
  const shutdown = async () => {
218
316
  if (isShutdown) {
219
317
  return;
220
318
  }
221
- otel.logger.info('shutdown started');
319
+ otel.logger.debug('shutdown started');
222
320
  isShutdown = true;
223
321
  // Force exit after timeout if cleanup hangs
224
322
  const forceExitTimer = setTimeout(() => {
@@ -228,7 +326,7 @@ export const createServer = <E extends Env>(app: Hono<E>, config?: AppConfig) =>
228
326
  try {
229
327
  await server.stop();
230
328
  await otel.shutdown();
231
- otel.logger.info('shutdown completed');
329
+ otel.logger.debug('shutdown completed');
232
330
  } finally {
233
331
  clearTimeout(forceExitTimer);
234
332
  }
@@ -269,10 +367,11 @@ const createAgentuityAPIs = () => {
269
367
  const router = new Hono<Env>();
270
368
  router.get('idle', (c) => {
271
369
  if (isIdle()) {
272
- return new Response('OK', { status: 200 });
370
+ return c.text('OK', { status: 200 });
273
371
  }
274
- return new Response('NO', { status: 200 });
372
+ return c.text('NO', { status: 200 });
275
373
  });
374
+ router.get('health', (c) => c.text('OK'));
276
375
  return router;
277
376
  };
278
377
 
@@ -282,11 +381,15 @@ const otelMiddleware = createMiddleware<Env>(async (c, next) => {
282
381
 
283
382
  const method = c.req.method;
284
383
  const url = new URL(c.req.url);
384
+ const threadProvider = getThreadProvider();
385
+ const sessionProvider = getSessionProvider();
386
+ const sessionEventProvider = getSessionEventProvider();
285
387
 
286
388
  // Execute the request handler within the extracted context
287
389
  await context.with(extractedContext, async (): Promise<void> => {
288
390
  // Create a span for this incoming request
289
- await trace.getTracer('http-server').startActiveSpan(
391
+ const tracer = trace.getTracer('http-server');
392
+ await tracer.startActiveSpan(
290
393
  `HTTP ${method}`,
291
394
  {
292
395
  kind: SpanKind.SERVER,
@@ -298,11 +401,126 @@ const otelMiddleware = createMiddleware<Env>(async (c, next) => {
298
401
  },
299
402
  },
300
403
  async (span): Promise<void> => {
404
+ const sctx = span.spanContext();
405
+ const sessionId = sctx?.traceId ? `sess_${sctx.traceId}` : generateId('sess');
406
+
407
+ // Add to tracestate
408
+ let traceState = sctx.traceState ?? new TraceState();
409
+ const projectId = runtimeConfig.getProjectId();
410
+ const orgId = runtimeConfig.getOrganizationId();
411
+ const deploymentId = runtimeConfig.getDeploymentId();
412
+ const isDevMode = runtimeConfig.isDevMode();
413
+ if (projectId) {
414
+ traceState = traceState.set('pid', projectId);
415
+ }
416
+ if (orgId) {
417
+ traceState = traceState.set('oid', orgId);
418
+ }
419
+ if (isDevMode) {
420
+ traceState = traceState.set('d', '1');
421
+ }
422
+ sctx.traceState = traceState;
423
+
424
+ const thread = await threadProvider.restore(c as unknown as HonoContext<Env>);
425
+ const session = await sessionProvider.restore(thread, sessionId);
426
+ const handler = new WaitUntilHandler(tracer);
427
+
428
+ const _c = privateContext(c);
429
+ const agentIds = new Set<string>();
430
+ _c.set('agentIds', agentIds);
431
+
432
+ const shouldSendSession = orgId && projectId && _c.var.routeId;
433
+ let canSendSessionEvents = true;
434
+
435
+ if (shouldSendSession) {
436
+ await sessionEventProvider
437
+ .start({
438
+ id: sessionId,
439
+ orgId,
440
+ projectId,
441
+ threadId: thread.id,
442
+ routeId: _c.var.routeId,
443
+ deploymentId,
444
+ devmode: isDevMode,
445
+ environment: runtimeConfig.getEnvironment(),
446
+ method: c.req.method,
447
+ url: c.req.url,
448
+ trigger: _c.var.trigger,
449
+ })
450
+ .catch((ex) => {
451
+ canSendSessionEvents = false;
452
+ c.var.logger.error('error sending session start event: %s', ex);
453
+ });
454
+ }
455
+
456
+ c.set('sessionId', sessionId);
457
+ c.set('thread', thread);
458
+ c.set('session', session);
459
+ _c.set('waitUntilHandler', handler);
460
+
461
+ let hasPendingWaits = false;
462
+
301
463
  try {
302
464
  await next();
303
- span.setStatus({
304
- code: SpanStatusCode.OK,
305
- });
465
+ if (handler?.hasPending()) {
466
+ hasPendingWaits = true;
467
+ handler
468
+ .waitUntilAll(c.var.logger, sessionId)
469
+ .then(async () => {
470
+ c.var.logger.debug('wait until finished for session %s', sessionId);
471
+ await sessionProvider.save(session);
472
+ await threadProvider.save(thread);
473
+ span.setStatus({ code: SpanStatusCode.OK });
474
+ if (shouldSendSession && canSendSessionEvents) {
475
+ sessionEventProvider
476
+ .complete({
477
+ id: sessionId,
478
+ statusCode: c.res.status,
479
+ agentIds: Array.from(agentIds),
480
+ })
481
+ .then(() => {})
482
+ .catch((ex) => c.var.logger.error(ex));
483
+ }
484
+ })
485
+ .catch((ex) => {
486
+ c.var.logger.error('wait until errored for session %s. %s', sessionId, ex);
487
+ if (ex instanceof Error) {
488
+ span.recordException(ex);
489
+ }
490
+ const message = (ex as Error).message ?? String(ex);
491
+ span.setStatus({
492
+ code: SpanStatusCode.ERROR,
493
+ message,
494
+ });
495
+ c.var.logger.error(message);
496
+ if (shouldSendSession && canSendSessionEvents) {
497
+ sessionEventProvider
498
+ .complete({
499
+ id: sessionId,
500
+ statusCode: c.res.status,
501
+ error: message,
502
+ agentIds: Array.from(agentIds),
503
+ })
504
+ .then(() => {})
505
+ .catch((ex) => c.var.logger.error(ex));
506
+ }
507
+ })
508
+ .finally(() => {
509
+ span.end();
510
+ });
511
+ } else {
512
+ span.setStatus({ code: SpanStatusCode.OK });
513
+ if (shouldSendSession && canSendSessionEvents) {
514
+ sessionEventProvider
515
+ .complete({
516
+ id: sessionId,
517
+ statusCode: c.res.status,
518
+ agentIds: Array.from(agentIds),
519
+ })
520
+ .then(() => {})
521
+ .catch((ex) => c.var.logger.error(ex));
522
+ }
523
+ }
306
524
  } catch (ex) {
307
525
  if (ex instanceof Error) {
308
526
  span.recordException(ex);
@@ -312,10 +530,34 @@ const otelMiddleware = createMiddleware<Env>(async (c, next) => {
312
530
  code: SpanStatusCode.ERROR,
313
531
  message,
314
532
  });
315
- c.var.logger.error('ERROR: %s', message);
533
+ c.var.logger.error(message);
534
+ if (shouldSendSession && canSendSessionEvents) {
535
+ sessionEventProvider
536
+ .complete({
537
+ id: sessionId,
538
+ statusCode: c.res.status,
539
+ error: message,
540
+ agentIds: Array.from(agentIds),
541
+ })
542
+ .then(() => {})
543
+ .catch((ex) => c.var.logger.error(ex));
544
+ }
316
545
  throw ex;
317
546
  } finally {
318
- span.end();
547
+ // add otel headers into HTTP response
548
+ const headers: Record<string, string> = {};
549
+ propagation.inject(context.active(), headers);
550
+ for (const key of Object.keys(headers)) {
551
+ c.header(key, headers[key]);
552
+ }
553
+ if (!hasPendingWaits) {
554
+ try {
555
+ await sessionProvider.save(session);
556
+ await threadProvider.save(thread);
557
+ } finally {
558
+ span.end();
559
+ }
560
+ }
319
561
  }
320
562
  }
321
563
  );