@agentuity/runtime 0.1.23 → 0.1.25

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 (38) hide show
  1. package/dist/_standalone.d.ts.map +1 -1
  2. package/dist/_standalone.js +11 -10
  3. package/dist/_standalone.js.map +1 -1
  4. package/dist/_tokens.d.ts.map +1 -1
  5. package/dist/_tokens.js +9 -8
  6. package/dist/_tokens.js.map +1 -1
  7. package/dist/agent.d.ts.map +1 -1
  8. package/dist/agent.js +228 -196
  9. package/dist/agent.js.map +1 -1
  10. package/dist/middleware.d.ts.map +1 -1
  11. package/dist/middleware.js +29 -6
  12. package/dist/middleware.js.map +1 -1
  13. package/dist/otel/fetch.js +1 -1
  14. package/dist/otel/fetch.js.map +1 -1
  15. package/dist/otel/otel.d.ts.map +1 -1
  16. package/dist/otel/otel.js +2 -29
  17. package/dist/otel/otel.js.map +1 -1
  18. package/dist/services/evalrun/http.d.ts.map +1 -1
  19. package/dist/services/evalrun/http.js +31 -2
  20. package/dist/services/evalrun/http.js.map +1 -1
  21. package/dist/services/local/keyvalue.d.ts.map +1 -1
  22. package/dist/services/local/keyvalue.js.map +1 -1
  23. package/dist/services/local/vector.d.ts.map +1 -1
  24. package/dist/services/local/vector.js.map +1 -1
  25. package/dist/services/session/http.d.ts.map +1 -1
  26. package/dist/services/session/http.js +58 -19
  27. package/dist/services/session/http.js.map +1 -1
  28. package/package.json +7 -9
  29. package/src/_standalone.ts +36 -10
  30. package/src/_tokens.ts +14 -12
  31. package/src/agent.ts +260 -259
  32. package/src/middleware.ts +39 -6
  33. package/src/otel/fetch.ts +1 -1
  34. package/src/otel/otel.ts +2 -30
  35. package/src/services/evalrun/http.ts +45 -10
  36. package/src/services/local/keyvalue.ts +3 -1
  37. package/src/services/local/vector.ts +3 -1
  38. package/src/services/session/http.ts +78 -33
package/src/middleware.ts CHANGED
@@ -290,7 +290,7 @@ export function createOtelMiddleware() {
290
290
  await context.with(extractedContext, async (): Promise<void> => {
291
291
  const tracer = trace.getTracer('http-server');
292
292
  await tracer.startActiveSpan(
293
- `HTTP ${method}`,
293
+ `${method} ${url.pathname}`,
294
294
  {
295
295
  kind: SpanKind.SERVER,
296
296
  attributes: {
@@ -373,8 +373,13 @@ export function createOtelMiddleware() {
373
373
  }
374
374
 
375
375
  // Factor out finalization logic so it can run synchronously or deferred
376
- const finalizeSession = async (statusCode?: number) => {
377
- internal.info('[session] saving session %s (thread: %s)', sessionId, thread.id);
376
+ const finalizeSession = async (statusCode?: number, error?: string) => {
377
+ internal.info(
378
+ '[session] saving session %s (thread: %s) (error: %s)',
379
+ sessionId,
380
+ thread.id,
381
+ error
382
+ );
378
383
  await sessionProvider.save(session);
379
384
  internal.info('[session] session saved, now saving thread');
380
385
  await threadProvider.save(thread);
@@ -397,12 +402,16 @@ export function createOtelMiddleware() {
397
402
  id: sessionId,
398
403
  threadId: isEmpty ? null : thread.id,
399
404
  statusCode: statusCode ?? c.res?.status ?? 200,
405
+ error,
400
406
  agentIds: agentIds?.length ? agentIds : undefined,
401
407
  userData,
402
408
  });
403
409
  internal.info('[session] session complete event sent');
404
410
  } catch (ex) {
405
- internal.info('[session] session complete event failed: %s', ex);
411
+ internal.info(
412
+ '[session] session complete event failed: %s',
413
+ ex instanceof Error ? ex.message : ex
414
+ );
406
415
  // Silently ignore session complete errors - don't block response
407
416
  }
408
417
  }
@@ -419,6 +428,13 @@ export function createOtelMiddleware() {
419
428
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
420
429
  const isStreaming = Boolean((c as any).get(IS_STREAMING_RESPONSE_KEY));
421
430
 
431
+ // Check if Hono caught an error (c.error is set by Hono's error handler)
432
+ // or if the response status indicates an error
433
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
434
+ const honoError = (c as any).error as Error | undefined;
435
+ const responseStatus = c.res?.status ?? 200;
436
+ const isError = honoError || responseStatus >= 500;
437
+
422
438
  if (isStreaming && streamDone) {
423
439
  // Defer session/thread saving until stream completes
424
440
  // This ensures thread state changes made during streaming are persisted
@@ -445,8 +461,21 @@ export function createOtelMiddleware() {
445
461
  });
446
462
 
447
463
  span.setStatus({ code: SpanStatusCode.OK });
464
+ } else if (isError) {
465
+ // Hono caught an error or response is 5xx - report as error
466
+ const errorMessage = honoError
467
+ ? (honoError.stack ?? honoError.message)
468
+ : `HTTP ${responseStatus}`;
469
+ span.setStatus({
470
+ code: SpanStatusCode.ERROR,
471
+ message: honoError?.message ?? errorMessage,
472
+ });
473
+ if (honoError) {
474
+ span.recordException(honoError);
475
+ }
476
+ await finalizeSession(responseStatus, errorMessage);
448
477
  } else {
449
- // Non-streaming: save session/thread synchronously (existing behavior)
478
+ // Non-streaming success: save session/thread synchronously
450
479
  await finalizeSession();
451
480
  span.setStatus({ code: SpanStatusCode.OK });
452
481
  }
@@ -454,10 +483,14 @@ export function createOtelMiddleware() {
454
483
  if (ex instanceof Error) {
455
484
  span.recordException(ex);
456
485
  }
486
+ const errorMessage = ex instanceof Error ? (ex.stack ?? ex.message) : String(ex);
457
487
  span.setStatus({
458
488
  code: SpanStatusCode.ERROR,
459
- message: (ex as Error).message ?? String(ex),
489
+ message: ex instanceof Error ? ex.message : String(ex),
460
490
  });
491
+
492
+ await finalizeSession(500, errorMessage);
493
+
461
494
  throw ex;
462
495
  } finally {
463
496
  const headers: Record<string, string> = {};
package/src/otel/fetch.ts CHANGED
@@ -37,7 +37,7 @@ export function instrumentFetch() {
37
37
 
38
38
  // Create a child span using the current context
39
39
  const childSpan = trace.getTracer('fetch').startSpan(
40
- `HTTP ${method}`,
40
+ `${method} ${_url.pathname}`,
41
41
  {
42
42
  attributes: {
43
43
  'http.url': url,
package/src/otel/otel.ts CHANGED
@@ -24,7 +24,6 @@ import {
24
24
  import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
25
25
  import { NodeSDK } from '@opentelemetry/sdk-node';
26
26
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
27
- import { initialize } from '@traceloop/node-server-sdk';
28
27
  import type { Logger } from '../logger';
29
28
  import { ConsoleLogRecordExporter, DebugSpanExporter } from './console';
30
29
  import { instrumentFetch } from './fetch';
@@ -308,35 +307,8 @@ export function registerOtel(config: OtelConfig): OtelResponse {
308
307
  instrumentationSDK.start();
309
308
  hostMetrics?.start();
310
309
 
311
- try {
312
- const projectName = config.projectId || '';
313
- const orgId = config.orgId || '';
314
- const appName = `${orgId}:${projectName}`;
315
-
316
- const traceloopHeaders: Record<string, string> = {};
317
- if (bearerToken) {
318
- traceloopHeaders.Authorization = `Bearer ${bearerToken}`;
319
- }
320
-
321
- initialize({
322
- appName,
323
- baseUrl: url,
324
- headers: traceloopHeaders,
325
- disableBatch: devmode,
326
- propagator,
327
- silenceInitializationMessage: true,
328
- traceloopSyncEnabled: false,
329
- tracingEnabled: false, // Disable traceloop's own tracing (equivalent to Python's telemetryEnabled: false)
330
- // Note: JavaScript SDK doesn't support resourceAttributes like Python
331
- });
332
- logger.debug(`Telemetry initialized with app_name: ${appName}`);
333
- logger.debug('Telemetry configured successfully');
334
- logger.debug('Sending telemetry data to %s', url);
335
- } catch (error) {
336
- logger.warn('Telemetry not available, skipping automatic instrumentation', {
337
- error: error instanceof Error ? error.message : String(error),
338
- });
339
- }
310
+ logger.debug('Telemetry configured successfully');
311
+ logger.debug('Sending telemetry data to %s', url);
340
312
  running = true;
341
313
  }
342
314
 
@@ -13,6 +13,7 @@ import {
13
13
  type Logger,
14
14
  StructuredError,
15
15
  } from '@agentuity/core';
16
+ import { context, trace, SpanStatusCode } from '@opentelemetry/api';
16
17
  import { internal } from '../../logger/internal';
17
18
 
18
19
  const EvalRunResponseError = StructuredError('EvalRunResponseError');
@@ -37,6 +38,10 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
37
38
  * @param event EvalRunStartEvent
38
39
  */
39
40
  async start(event: EvalRunStartEvent): Promise<void> {
41
+ const tracer = trace.getTracer('evalrun');
42
+ const currentContext = context.active();
43
+ const span = tracer.startSpan('Eval Start', {}, currentContext);
44
+
40
45
  const endpoint = '/evalrun/2025-03-17';
41
46
  const fullUrl = `${this.baseUrl}${endpoint}`;
42
47
 
@@ -57,18 +62,24 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
57
62
  internal.info('[EVALRUN HTTP] ============================================');
58
63
 
59
64
  try {
60
- const resp = await this.apiClient.post(
61
- endpoint,
62
- payload,
63
- APIResponseSchemaNoData(),
64
- EvalRunStartEventDelayedSchema
65
+ const spanContext = trace.setSpan(currentContext, span);
66
+ const resp = await context.with(spanContext, () =>
67
+ this.apiClient.post(
68
+ endpoint,
69
+ payload,
70
+ APIResponseSchemaNoData(),
71
+ EvalRunStartEventDelayedSchema
72
+ )
65
73
  );
74
+
66
75
  if (resp.success) {
67
76
  this.logger.debug('[EVALRUN HTTP] Start event sent successfully: %s', event.id);
77
+ span.setStatus({ code: SpanStatusCode.OK });
68
78
  return;
69
79
  }
70
80
  const errorMsg = resp.message || 'Unknown error';
71
81
  this.logger.error('[EVALRUN HTTP] Start event failed: %s, error: %s', event.id, errorMsg);
82
+ span.setStatus({ code: SpanStatusCode.ERROR, message: errorMsg });
72
83
  throw new EvalRunResponseError({ message: errorMsg });
73
84
  } catch (error) {
74
85
  this.logger.error(
@@ -86,7 +97,14 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
86
97
  JSON.stringify(error.issues, null, 2)
87
98
  );
88
99
  }
100
+ span.recordException(error as Error);
101
+ span.setStatus({
102
+ code: SpanStatusCode.ERROR,
103
+ message: error instanceof Error ? error.message : String(error),
104
+ });
89
105
  throw error;
106
+ } finally {
107
+ span.end();
90
108
  }
91
109
  }
92
110
 
@@ -96,6 +114,10 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
96
114
  * @param event EvalRunCompleteEvent
97
115
  */
98
116
  async complete(event: EvalRunCompleteEvent): Promise<void> {
117
+ const tracer = trace.getTracer('evalrun');
118
+ const currentContext = context.active();
119
+ const span = tracer.startSpan('Eval End', {}, currentContext);
120
+
99
121
  const endpoint = '/evalrun/2025-03-17';
100
122
  const fullUrl = `${this.baseUrl}${endpoint}`;
101
123
  this.logger.debug('[EVALRUN HTTP] Sending eval run complete event: %s', event.id);
@@ -103,14 +125,19 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
103
125
  this.logger.debug('[EVALRUN HTTP] Base URL: %s', this.baseUrl);
104
126
 
105
127
  try {
106
- const resp = await this.apiClient.put(
107
- endpoint,
108
- { ...event, timestamp: Date.now() },
109
- APIResponseSchemaNoData(),
110
- EvalRunCompleteEventDelayedSchema
128
+ const spanContext = trace.setSpan(currentContext, span);
129
+ const resp = await context.with(spanContext, () =>
130
+ this.apiClient.put(
131
+ endpoint,
132
+ { ...event, timestamp: Date.now() },
133
+ APIResponseSchemaNoData(),
134
+ EvalRunCompleteEventDelayedSchema
135
+ )
111
136
  );
137
+
112
138
  if (resp.success) {
113
139
  this.logger.debug('[EVALRUN HTTP] Complete event sent successfully: %s', event.id);
140
+ span.setStatus({ code: SpanStatusCode.OK });
114
141
  return;
115
142
  }
116
143
  const errorMsg = resp.message || 'Unknown error';
@@ -119,6 +146,7 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
119
146
  event.id,
120
147
  errorMsg
121
148
  );
149
+ span.setStatus({ code: SpanStatusCode.ERROR, message: errorMsg });
122
150
  throw new EvalRunResponseError({ message: errorMsg });
123
151
  } catch (error) {
124
152
  this.logger.error(
@@ -126,7 +154,14 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
126
154
  event.id,
127
155
  error instanceof Error ? error.message : String(error)
128
156
  );
157
+ span.recordException(error as Error);
158
+ span.setStatus({
159
+ code: SpanStatusCode.ERROR,
160
+ message: error instanceof Error ? error.message : String(error),
161
+ });
129
162
  throw error;
163
+ } finally {
164
+ span.end();
130
165
  }
131
166
  }
132
167
  }
@@ -137,7 +137,9 @@ export class LocalKeyValueStorage implements KeyValueStorage {
137
137
  throw new Error('getStats not implemented for local storage');
138
138
  }
139
139
 
140
- async getAllStats(_params?: GetAllStatsParams): Promise<Record<string, KeyValueStats> | KeyValueStatsPaginated> {
140
+ async getAllStats(
141
+ _params?: GetAllStatsParams
142
+ ): Promise<Record<string, KeyValueStats> | KeyValueStatsPaginated> {
141
143
  throw new Error('getAllStats not implemented for local storage');
142
144
  }
143
145
 
@@ -347,7 +347,9 @@ export class LocalVectorStorage implements VectorStorage {
347
347
  };
348
348
  }
349
349
 
350
- async getAllStats(_params?: VectorGetAllStatsParams): Promise<Record<string, VectorNamespaceStats> | VectorStatsPaginated> {
350
+ async getAllStats(
351
+ _params?: VectorGetAllStatsParams
352
+ ): Promise<Record<string, VectorNamespaceStats> | VectorStatsPaginated> {
351
353
  const query = this.#db.query(`
352
354
  SELECT name, embedding, document
353
355
  FROM vector_storage
@@ -8,6 +8,7 @@ import {
8
8
  type Logger,
9
9
  StructuredError,
10
10
  } from '@agentuity/core';
11
+ import { context, trace, SpanStatusCode } from '@opentelemetry/api';
11
12
  import { internal } from '../../logger/internal';
12
13
 
13
14
  const SessionResponseError = StructuredError('SessionResponseError');
@@ -54,22 +55,44 @@ export class HTTPSessionEventProvider implements SessionEventProvider {
54
55
  return;
55
56
  }
56
57
 
57
- internal.info('[session-http] sending start event: %s', event.id);
58
- this.logger.debug('Sending session start event: %s', event.id);
59
- const resp = await this.apiClient.post(
60
- '/session/2025-03-17',
61
- { ...event, timestamp: Date.now() },
62
- APIResponseSchemaNoData(),
63
- SessionStartEventDelayedSchema
64
- );
65
- if (resp.success) {
66
- internal.info('[session-http] start event sent successfully: %s', event.id);
67
- this.logger.debug('Session start event sent successfully: %s', event.id);
68
- this.startedSessions.add(event.id);
69
- return;
58
+ const tracer = trace.getTracer('session');
59
+ const currentContext = context.active();
60
+ const span = tracer.startSpan('Session Start', {}, currentContext);
61
+
62
+ try {
63
+ internal.info('[session-http] sending start event: %s', event.id);
64
+ this.logger.debug('Sending session start event: %s', event.id);
65
+
66
+ const spanContext = trace.setSpan(currentContext, span);
67
+ const resp = await context.with(spanContext, () =>
68
+ this.apiClient.post(
69
+ '/session/2025-03-17',
70
+ { ...event, timestamp: Date.now() },
71
+ APIResponseSchemaNoData(),
72
+ SessionStartEventDelayedSchema
73
+ )
74
+ );
75
+
76
+ if (resp.success) {
77
+ internal.info('[session-http] start event sent successfully: %s', event.id);
78
+ this.logger.debug('Session start event sent successfully: %s', event.id);
79
+ this.startedSessions.add(event.id);
80
+ span.setStatus({ code: SpanStatusCode.OK });
81
+ return;
82
+ }
83
+ internal.info('[session-http] start event failed: %s - %s', event.id, resp.message);
84
+ span.setStatus({ code: SpanStatusCode.ERROR, message: resp.message });
85
+ throw new SessionResponseError({ message: resp.message });
86
+ } catch (error) {
87
+ span.recordException(error as Error);
88
+ span.setStatus({
89
+ code: SpanStatusCode.ERROR,
90
+ message: error instanceof Error ? error.message : String(error),
91
+ });
92
+ throw error;
93
+ } finally {
94
+ span.end();
70
95
  }
71
- internal.info('[session-http] start event failed: %s - %s', event.id, resp.message);
72
- throw new SessionResponseError({ message: resp.message });
73
96
  }
74
97
 
75
98
  /**
@@ -91,24 +114,46 @@ export class HTTPSessionEventProvider implements SessionEventProvider {
91
114
  }
92
115
  this.startedSessions.delete(event.id);
93
116
 
94
- internal.info(
95
- '[session-http] sending complete event: %s, userData: %s',
96
- event.id,
97
- event.userData ? `${event.userData.length} bytes` : 'none'
98
- );
99
- this.logger.debug('Sending session complete event: %s', event.id);
100
- const resp = await this.apiClient.put(
101
- '/session/2025-03-17',
102
- { ...event, timestamp: Date.now() },
103
- APIResponseSchemaNoData(),
104
- SessionCompleteEventDelayedSchema
105
- );
106
- if (resp.success) {
107
- internal.info('[session-http] complete event sent successfully: %s', event.id);
108
- this.logger.debug('Session complete event sent successfully: %s', event.id);
109
- return;
117
+ const tracer = trace.getTracer('session');
118
+ const currentContext = context.active();
119
+ const span = tracer.startSpan('Session End', {}, currentContext);
120
+
121
+ try {
122
+ internal.info(
123
+ '[session-http] sending complete event: %s, userData: %s',
124
+ event.id,
125
+ event.userData ? `${event.userData.length} bytes` : 'none'
126
+ );
127
+ this.logger.debug('Sending session complete event: %s', event.id);
128
+
129
+ const spanContext = trace.setSpan(currentContext, span);
130
+ const resp = await context.with(spanContext, () =>
131
+ this.apiClient.put(
132
+ '/session/2025-03-17',
133
+ { ...event, timestamp: Date.now() },
134
+ APIResponseSchemaNoData(),
135
+ SessionCompleteEventDelayedSchema
136
+ )
137
+ );
138
+
139
+ if (resp.success) {
140
+ internal.info('[session-http] complete event sent successfully: %s', event.id);
141
+ this.logger.debug('Session complete event sent successfully: %s', event.id);
142
+ span.setStatus({ code: SpanStatusCode.OK });
143
+ return;
144
+ }
145
+ internal.info('[session-http] complete event failed: %s - %s', event.id, resp.message);
146
+ span.setStatus({ code: SpanStatusCode.ERROR, message: resp.message });
147
+ throw new SessionResponseError({ message: resp.message });
148
+ } catch (error) {
149
+ span.recordException(error as Error);
150
+ span.setStatus({
151
+ code: SpanStatusCode.ERROR,
152
+ message: error instanceof Error ? error.message : String(error),
153
+ });
154
+ throw error;
155
+ } finally {
156
+ span.end();
110
157
  }
111
- internal.info('[session-http] complete event failed: %s - %s', event.id, resp.message);
112
- throw new SessionResponseError({ message: resp.message });
113
158
  }
114
159
  }