@smythos/sre 1.7.9 → 1.7.15

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 (45) hide show
  1. package/CHANGELOG +7 -0
  2. package/dist/index.js +48 -48
  3. package/dist/index.js.map +1 -1
  4. package/dist/types/Components/APICall/OAuth.helper.d.ts +1 -1
  5. package/dist/types/Core/ConnectorsService.d.ts +3 -1
  6. package/dist/types/Core/HookService.d.ts +18 -0
  7. package/dist/types/helpers/Conversation.helper.d.ts +3 -0
  8. package/dist/types/index.d.ts +7 -3
  9. package/dist/types/subsystems/IO/VectorDB.service/embed/BaseEmbedding.d.ts +2 -1
  10. package/dist/types/subsystems/ObservabilityManager/Log.service/LogConnector.d.ts +19 -0
  11. package/dist/types/subsystems/ObservabilityManager/Log.service/connectors/ConsoleLog.class.d.ts +13 -0
  12. package/dist/types/subsystems/ObservabilityManager/Log.service/connectors/OTel/OTel.class.d.ts +59 -0
  13. package/dist/types/subsystems/ObservabilityManager/Log.service/connectors/OTel/OTelContextRegistry.d.ts +16 -0
  14. package/dist/types/subsystems/ObservabilityManager/Log.service/index.d.ts +4 -0
  15. package/dist/types/subsystems/ObservabilityManager/Telemetry.service/TelemetryConnector.d.ts +13 -0
  16. package/dist/types/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.class.d.ts +59 -0
  17. package/dist/types/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTelContextRegistry.d.ts +16 -0
  18. package/dist/types/subsystems/ObservabilityManager/Telemetry.service/index.d.ts +4 -0
  19. package/dist/types/types/LLM.types.d.ts +3 -1
  20. package/dist/types/types/SRE.types.d.ts +5 -2
  21. package/package.json +9 -1
  22. package/src/Components/APICall/OAuth.helper.ts +24 -25
  23. package/src/Components/APICall/mimeTypeCategories.ts +16 -1
  24. package/src/Components/Component.class.ts +3 -1
  25. package/src/Core/ConnectorsService.ts +5 -1
  26. package/src/Core/HookService.ts +104 -12
  27. package/src/Core/boot.ts +3 -1
  28. package/src/helpers/Conversation.helper.ts +42 -15
  29. package/src/index.ts +212 -208
  30. package/src/index.ts.bak +212 -208
  31. package/src/subsystems/AgentManager/Agent.class.ts +9 -8
  32. package/src/subsystems/AgentManager/AgentRuntime.class.ts +4 -4
  33. package/src/subsystems/AgentManager/OSResourceMonitor.ts +69 -8
  34. package/src/subsystems/IO/VectorDB.service/embed/BaseEmbedding.ts +7 -3
  35. package/src/subsystems/IO/VectorDB.service/embed/index.ts +1 -0
  36. package/src/subsystems/LLMManager/LLM.service/connectors/Echo.class.ts +1 -1
  37. package/src/subsystems/{IO → ObservabilityManager}/Log.service/LogConnector.ts +6 -1
  38. package/src/subsystems/{IO → ObservabilityManager}/Log.service/connectors/ConsoleLog.class.ts +6 -3
  39. package/src/subsystems/{IO → ObservabilityManager}/Log.service/index.ts +1 -2
  40. package/src/subsystems/ObservabilityManager/Telemetry.service/TelemetryConnector.ts +23 -0
  41. package/src/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.class.ts +759 -0
  42. package/src/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTelContextRegistry.ts +35 -0
  43. package/src/subsystems/ObservabilityManager/Telemetry.service/index.ts +12 -0
  44. package/src/types/LLM.types.ts +2 -0
  45. package/src/types/SRE.types.ts +4 -1
@@ -0,0 +1,759 @@
1
+ import { Logger } from '@sre/helpers/Log.helper';
2
+ import { AccessRequest } from '@sre/Security/AccessControl/AccessRequest.class';
3
+ import { ACL } from '@sre/Security/AccessControl/ACL.class';
4
+ import { IAccessCandidate } from '@sre/types/ACL.types';
5
+ import { TelemetryConnector } from '../../TelemetryConnector';
6
+ import { AgentCallLog } from '@sre/types/AgentLogger.types';
7
+
8
+ import { trace, context, SpanStatusCode, Tracer } from '@opentelemetry/api';
9
+ import { Logger as OTelLogger, logs, SeverityNumber } from '@opentelemetry/api-logs';
10
+ import { OTelContextRegistry } from './OTelContextRegistry';
11
+ import { HookService, THook } from '@sre/Core/HookService';
12
+
13
+ // OpenTelemetry SDK and Exporters
14
+ import { resourceFromAttributes } from '@opentelemetry/resources';
15
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
16
+ import { NodeTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
17
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
18
+ import { LoggerProvider, SimpleLogRecordProcessor, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
19
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
20
+ import { IAgent } from '@sre/types/Agent.types';
21
+ import { Conversation } from '@sre/helpers/Conversation.helper';
22
+ import { TLLMEvent } from '@sre/types/LLM.types';
23
+
24
+ const outputLogger = Logger('OTelLog');
25
+
26
+ export type OTelLogConfig = {
27
+ endpoint: string;
28
+ headers: Record<string, string>;
29
+ serviceName?: string;
30
+ serviceVersion?: string;
31
+ /**
32
+ * Maximum size (in bytes) for full output in logs.
33
+ * Outputs larger than this will be truncated with a note.
34
+ * Default: 256KB (262144 bytes) - Safe for all backends (Loki, Elasticsearch, etc.)
35
+ *
36
+ * Common values:
37
+ * - 256KB (262144) - Recommended, works with all backends
38
+ * - 512KB (524288) - Works with most backends
39
+ * - 1MB (1048576) - Only for backends that support it (CloudWatch, Datadog)
40
+ */
41
+ maxOutputSize?: number;
42
+ /**
43
+ * Only log full output on errors. Success cases will only log size/preview.
44
+ * Default: false (log full output for both success and errors)
45
+ */
46
+ fullOutputOnErrorOnly?: boolean;
47
+ /**
48
+ * Fields to redact from outputs (e.g., ['password', 'token', 'apiKey'])
49
+ * These will be replaced with '[REDACTED]' in logs
50
+ */
51
+ redactFields?: string[];
52
+ };
53
+
54
+ export class OTel extends TelemetryConnector {
55
+ public name: string = 'OTel';
56
+ public id: string;
57
+ private tracer: Tracer;
58
+ private logger: OTelLogger;
59
+ private tracerProvider: NodeTracerProvider;
60
+ private loggerProvider: LoggerProvider;
61
+
62
+ constructor(protected _settings: OTelLogConfig) {
63
+ super();
64
+
65
+ outputLogger.log(`Initializing Tracer ...`);
66
+
67
+ // Initialize Trace Exporter and Provider
68
+ const traceExporter = new OTLPTraceExporter({
69
+ url: `${_settings.endpoint}/v1/traces`,
70
+ headers: _settings.headers,
71
+ });
72
+
73
+ const spanProcessor = new BatchSpanProcessor(traceExporter);
74
+
75
+ // Create resource with service information
76
+ const resource = resourceFromAttributes({
77
+ [ATTR_SERVICE_NAME]: _settings.serviceName || 'smythos',
78
+ [ATTR_SERVICE_VERSION]: _settings.serviceVersion || '1.0.0',
79
+ });
80
+
81
+ // TypeScript definitions are incomplete, but this works at runtime
82
+ this.tracerProvider = new NodeTracerProvider({
83
+ resource,
84
+ spanProcessors: [spanProcessor],
85
+ } as any);
86
+
87
+ this.tracerProvider.register();
88
+
89
+ outputLogger.log(`Initializing Log Exporter ...`);
90
+ // Initialize Log Exporter and Provider
91
+ const logExporter = new OTLPLogExporter({
92
+ url: `${_settings.endpoint}/v1/logs`,
93
+ headers: _settings.headers,
94
+ });
95
+
96
+ //const logProcessor = new SimpleLogRecordProcessor(logExporter as any);
97
+ const logProcessor = new BatchLogRecordProcessor(logExporter as any);
98
+
99
+ // TypeScript definitions are incomplete, but this works at runtime
100
+ this.loggerProvider = new LoggerProvider({
101
+ resource,
102
+ processors: [logProcessor],
103
+ } as any);
104
+
105
+ logs.setGlobalLoggerProvider(this.loggerProvider);
106
+
107
+ // Now get tracer and logger from the initialized providers
108
+ this.tracer = trace.getTracer('smythos.agent');
109
+ this.logger = logs.getLogger('smythos.agent');
110
+
111
+ this.id = `otel-${_settings.endpoint}`;
112
+ this.setupHooks();
113
+ }
114
+
115
+ /**
116
+ * Cleanup and shutdown exporters
117
+ */
118
+ public async stop(): Promise<void> {
119
+ outputLogger.log(`Stopping ${this.name} connector ...`);
120
+ // TypeScript definitions are incomplete for these methods
121
+ await (this.tracerProvider as any).forceFlush?.().catch((error) => {
122
+ outputLogger.error('Error forcing flush of tracer provider', error);
123
+ });
124
+ await (this.tracerProvider as any).shutdown?.().catch((error) => {
125
+ outputLogger.error('Error shutting down tracer provider', error);
126
+ });
127
+ await this.loggerProvider.forceFlush().catch((error) => {
128
+ outputLogger.error('Error forcing flush of logger provider', error);
129
+ });
130
+ await this.loggerProvider.shutdown().catch((error) => {
131
+ outputLogger.error('Error shutting down logger provider', error);
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Redact sensitive fields from an object
137
+ */
138
+ private redactSensitiveData(data: any, redactFields?: string[]): any {
139
+ if (!redactFields || redactFields.length === 0) return data;
140
+ if (typeof data !== 'object' || data === null) return data;
141
+
142
+ const redacted = Array.isArray(data) ? [...data] : { ...data };
143
+
144
+ for (const key in redacted) {
145
+ if (redactFields.some((field) => key.toLowerCase().includes(field.toLowerCase()))) {
146
+ redacted[key] = '[REDACTED]';
147
+ } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
148
+ redacted[key] = this.redactSensitiveData(redacted[key], redactFields);
149
+ }
150
+ }
151
+
152
+ return redacted;
153
+ }
154
+
155
+ /**
156
+ * Safely format output for logging with size limits and redaction
157
+ */
158
+ private formatOutputForLog(output: any, isError: boolean = false): string | undefined {
159
+ const config = this._settings;
160
+ const maxSize = config.maxOutputSize ?? 10 * 1024; // Default 10KB - safe for all backends
161
+ const errorOnly = config.fullOutputOnErrorOnly ?? false;
162
+
163
+ // If error-only mode and this is success, return undefined
164
+ if (errorOnly && !isError) {
165
+ return undefined;
166
+ }
167
+
168
+ // Redact sensitive fields
169
+ const redacted = this.redactSensitiveData(output, config.redactFields);
170
+
171
+ // Stringify
172
+ const outputStr = JSON.stringify(redacted);
173
+
174
+ // Check size limit
175
+ if (outputStr && outputStr.length > maxSize) {
176
+ const preview = outputStr.substring(0, maxSize);
177
+ return `${preview}...[TRUNCATED: ${outputStr.length} bytes, limit: ${maxSize} bytes]`;
178
+ }
179
+
180
+ return outputStr;
181
+ }
182
+ public getResourceACL(resourceId: string, candidate: IAccessCandidate): Promise<ACL> {
183
+ return Promise.resolve(new ACL());
184
+ }
185
+ protected log(acRequest: AccessRequest, logData: AgentCallLog, callId?: string): Promise<any> {
186
+ return Promise.resolve();
187
+ }
188
+ protected logTask(acRequest: AccessRequest, tasks: number, isUsingTestDomain: boolean): Promise<void> {
189
+ return Promise.resolve();
190
+ }
191
+
192
+ private prepareComponentData(data, prefix?: string, maxEntryLength = 200) {
193
+ const result = {};
194
+
195
+ for (let key in data) {
196
+ result[prefix ? `${prefix}.${key}` : key] = (typeof data[key] === 'object' ? JSON.stringify(data[key]) : data[key].toString()).substring(
197
+ 0,
198
+ maxEntryLength
199
+ );
200
+ }
201
+
202
+ return result;
203
+ }
204
+ protected setupHooks(): Promise<void> {
205
+ const tracer = this.tracer;
206
+ const logger = this.logger;
207
+ const oTelInstance = this;
208
+
209
+ const createToolInfoHandler = function (hookContext) {
210
+ return function (toolInfo: any) {
211
+ if (!hookContext.curLLMGenSpan || !hookContext.convSpan) return;
212
+
213
+ const modelId = toolInfo.model;
214
+ const contextWindow = toolInfo.contextWindow;
215
+ const lastContext = contextWindow.filter((context) => context.role === 'user').slice(-2);
216
+
217
+ const toolNames = toolInfo.map((tool) => tool.name + '(' + tool.arguments + ')');
218
+ hookContext.curLLMGenSpan.addEvent('llm.gen.tool.calls', {
219
+ 'tool.calls': toolNames.join(', '),
220
+ 'llm.model': modelId || 'unknown',
221
+ 'context.preview': JSON.stringify(lastContext).substring(0, 200),
222
+ });
223
+
224
+ const spanContext = trace.setSpan(context.active(), hookContext.curLLMGenSpan);
225
+ context.with(spanContext, () => {
226
+ logger.emit({
227
+ severityNumber: SeverityNumber.INFO,
228
+ severityText: 'INFO',
229
+ body: `LLM tool calls: ${toolNames.join(', ')}`,
230
+ attributes: {
231
+ 'agent.id': hookContext.agentId,
232
+ 'conv.id': hookContext.processId,
233
+ 'llm.model': modelId || 'unknown',
234
+ 'context.preview': JSON.stringify(lastContext).substring(0, 5000),
235
+ },
236
+ });
237
+ });
238
+
239
+ hookContext.curLLMGenSpan.end();
240
+ delete hookContext.curLLMGenSpan;
241
+ };
242
+ };
243
+
244
+ const createDataHandler = function (hookContext) {
245
+ return function (data: any, reqInfo: any) {
246
+ if (!hookContext.convSpan) return;
247
+ if (hookContext.curLLMGenSpan) return;
248
+
249
+ const modelId = reqInfo.model;
250
+ const contextWindow = reqInfo.contextWindow;
251
+
252
+ const lastContext = contextWindow.filter((context) => context.role === 'user').slice(-2);
253
+ // End TTFB span when first data arrives
254
+ if (hookContext?.latencySpans?.[reqInfo.requestId]) {
255
+ const ttfbSpan = hookContext.latencySpans[reqInfo.requestId];
256
+
257
+ // Calculate actual TTFB duration and add as attribute
258
+ ttfbSpan.addEvent('llm.first.byte.received', {
259
+ 'request.id': reqInfo.requestId,
260
+ 'data.size': JSON.stringify(data || {}).length,
261
+ 'llm.model': modelId || 'unknown',
262
+ });
263
+
264
+ ttfbSpan.setStatus({ code: SpanStatusCode.OK });
265
+ ttfbSpan.end();
266
+
267
+ delete hookContext.latencySpans[reqInfo.requestId];
268
+ }
269
+
270
+ const llmGenSpan = tracer.startSpan(
271
+ 'Conv.GenAI',
272
+ {
273
+ attributes: {
274
+ 'agent.id': hookContext.agentId,
275
+ 'conv.id': hookContext.processId,
276
+ 'llm.model': modelId || 'unknown',
277
+ },
278
+ },
279
+ trace.setSpan(context.active(), hookContext.convSpan)
280
+ );
281
+ llmGenSpan.addEvent('llm.gen.started', {
282
+ 'request.id': reqInfo.requestId,
283
+ timestamp: Date.now(),
284
+ 'llm.model': modelId || 'unknown',
285
+ 'context.preview': JSON.stringify(lastContext).substring(0, 200),
286
+ });
287
+ hookContext.curLLMGenSpan = llmGenSpan;
288
+ };
289
+ };
290
+
291
+ const createRequestedHandler = function (hookContext) {
292
+ return function (reqInfo: any) {
293
+ if (!hookContext.convSpan) return;
294
+
295
+ if (!hookContext.latencySpans) hookContext.latencySpans = {};
296
+ const contextWindow = reqInfo.contextWindow;
297
+
298
+ const lastContext = contextWindow.filter((context) => context.role === 'user').slice(-2);
299
+
300
+ const modelId = reqInfo.model;
301
+ const llmGenLatencySpan = tracer.startSpan(
302
+ 'Conv.GenAI.TTFB',
303
+ {
304
+ attributes: {
305
+ 'agent.id': hookContext.agentId,
306
+ 'conv.id': hookContext.processId,
307
+ 'request.id': reqInfo.requestId,
308
+ 'llm.model': modelId || 'unknown',
309
+ 'metric.type': 'ttfb',
310
+ },
311
+ },
312
+ trace.setSpan(context.active(), hookContext.convSpan)
313
+ );
314
+ llmGenLatencySpan.addEvent('llm.requested', {
315
+ 'request.id': reqInfo.requestId,
316
+ timestamp: Date.now(),
317
+ 'context.preview': JSON.stringify(lastContext).substring(0, 200),
318
+ });
319
+ hookContext.latencySpans[reqInfo.requestId] = llmGenLatencySpan;
320
+ };
321
+ };
322
+ HookService.register(
323
+ 'Conversation.streamPrompt',
324
+ async function (additionalContext, args) {
325
+ const conversation: Conversation = this.instance;
326
+ const processId = conversation.id;
327
+ const agentId = conversation.agentId;
328
+ const message = args?.message || null;
329
+ const hookContext: any = this.context;
330
+ if (message == null) {
331
+ //this is a conversation step, will be handled by createRequestedHandler
332
+
333
+ return;
334
+ }
335
+
336
+ const modelId = typeof conversation?.model === 'string' ? conversation?.model : conversation?.model?.modelId;
337
+
338
+ const convSpan = tracer.startSpan('Agent.Conv', {
339
+ attributes: {
340
+ 'agent.id': agentId,
341
+ 'conv.id': processId,
342
+ 'llm.model': modelId || 'unknown',
343
+ },
344
+ });
345
+ hookContext.convSpan = convSpan;
346
+
347
+ hookContext.dataHandler = createDataHandler(hookContext);
348
+ conversation.on(TLLMEvent.Data, hookContext.dataHandler);
349
+ hookContext.requestedHandler = createRequestedHandler(hookContext);
350
+ conversation.on(TLLMEvent.Requested, hookContext.requestedHandler);
351
+ hookContext.agentId = agentId;
352
+ hookContext.processId = processId;
353
+ hookContext.toolInfoHandler = createToolInfoHandler(hookContext);
354
+
355
+ conversation.on(TLLMEvent.ToolInfo, hookContext.toolInfoHandler);
356
+
357
+ // Add start event
358
+
359
+ convSpan.addEvent('skill.process.started', {
360
+ 'input.size': JSON.stringify(message || {}).length,
361
+ 'input.preview': message.substring(0, 200),
362
+ 'llm.model': modelId || 'unknown',
363
+ });
364
+
365
+ OTelContextRegistry.startProcess(agentId, processId, convSpan);
366
+
367
+ const spanCtx = convSpan.spanContext();
368
+ const spanContext = trace.setSpan(context.active(), convSpan);
369
+ context.with(spanContext, () => {
370
+ logger.emit({
371
+ severityNumber: SeverityNumber.INFO,
372
+ severityText: 'INFO',
373
+ body: `Conversation.streamPrompt started: ${processId}`,
374
+ attributes: {
375
+ // Explicit trace correlation (some backends need these)
376
+ trace_id: spanCtx.traceId,
377
+ span_id: spanCtx.spanId,
378
+ trace_flags: spanCtx.traceFlags,
379
+
380
+ 'agent.id': agentId,
381
+ 'conv.id': processId,
382
+ 'input.size': JSON.stringify(message || {}).length,
383
+ 'input.preview': message.substring(0, 2000),
384
+ },
385
+ });
386
+ });
387
+ },
388
+ THook.NonBlocking
389
+ );
390
+
391
+ HookService.registerAfter(
392
+ 'Conversation.streamPrompt',
393
+ async function ({ result, args, error }) {
394
+ const conversation: Conversation = this.instance;
395
+ const processId = conversation.id;
396
+ const agentId = conversation.agentId;
397
+ const message = args?.[0] || null;
398
+ const hookContext: any = this.context;
399
+ if (message == null) {
400
+ return;
401
+ }
402
+
403
+ const ctx = OTelContextRegistry.get(agentId, processId);
404
+ if (!ctx) return;
405
+
406
+ if (hookContext.curLLMGenSpan) {
407
+ hookContext.curLLMGenSpan.addEvent('llm.gen.content', {
408
+ 'content.size': JSON.stringify(result || {}).length,
409
+ 'content.preview': result.substring(0, 200),
410
+ });
411
+ hookContext.curLLMGenSpan.end();
412
+
413
+ if (hookContext.toolInfoHandler) conversation.off(TLLMEvent.ToolInfo, hookContext.toolInfoHandler);
414
+ if (hookContext.dataHandler) conversation.off(TLLMEvent.Data, hookContext.dataHandler);
415
+ if (hookContext.requestedHandler) conversation.off(TLLMEvent.Requested, hookContext.requestedHandler);
416
+ }
417
+
418
+ const { rootSpan: convSpan } = ctx;
419
+
420
+ const spanCtx = convSpan.spanContext();
421
+ const spanContext = trace.setSpan(context.active(), convSpan);
422
+ context.with(spanContext, () => {
423
+ logger.emit({
424
+ severityNumber: SeverityNumber.INFO,
425
+ severityText: 'INFO',
426
+ body: `Conversation.streamPrompt completed: ${processId}`,
427
+ attributes: {
428
+ 'agent.id': agentId,
429
+ 'conv.id': processId,
430
+ 'output.size': JSON.stringify(result || {}).length,
431
+ 'output.preview': result.substring(0, 2000),
432
+ },
433
+ });
434
+ });
435
+
436
+ convSpan.end();
437
+
438
+ OTelContextRegistry.endProcess(agentId, processId);
439
+ },
440
+ THook.NonBlocking
441
+ );
442
+
443
+ HookService.register(
444
+ 'SREAgent.process',
445
+ async function (endpointPath, inputData) {
446
+ const agent: IAgent = this.instance;
447
+ // nested process has a subID that needs to be removed
448
+ // a process can be nested if it was called by a parent process : e.g conversation => agent , agent => sub-agent, agent => forked process ....etc
449
+ const agentProcessId = agent.agentRuntime.processID;
450
+ const processId = agentProcessId.split(':').shift();
451
+
452
+ const agentId = agent.id;
453
+ const agentRequest = agent.agentRequest;
454
+ const teamId = agent.teamId;
455
+ const _hookContext: any = this.context;
456
+
457
+ const body = oTelInstance.prepareComponentData(agentRequest.body || {});
458
+ const query = oTelInstance.prepareComponentData(agentRequest.query || {});
459
+ const headers = oTelInstance.prepareComponentData(agentRequest.headers || {});
460
+ const agentInput = oTelInstance.prepareComponentData(inputData || {});
461
+
462
+ const input = { body, query, headers, processInput: agentInput };
463
+
464
+ let convSpan;
465
+ const ctx = OTelContextRegistry.get(agentId, processId);
466
+
467
+ if (ctx) {
468
+ convSpan = ctx.rootSpan;
469
+ _hookContext.otelSpan = convSpan;
470
+ }
471
+ const agentSpan = tracer.startSpan(
472
+ 'Agent.Skill',
473
+ {
474
+ attributes: {
475
+ 'agent.id': agentId,
476
+ 'team.id': teamId,
477
+ 'process.id': agentProcessId,
478
+ },
479
+ },
480
+ convSpan ? trace.setSpan(context.active(), convSpan) : undefined
481
+ );
482
+
483
+ // Add start event
484
+ const inputPreview = JSON.stringify(input || {}).substring(0, 200);
485
+ agentSpan.addEvent('skill.process.started', {
486
+ endpoint: endpointPath,
487
+ 'input.size': JSON.stringify(input || {}).length,
488
+ 'input.preview': inputPreview,
489
+ });
490
+
491
+ OTelContextRegistry.startProcess(agentId, agentProcessId, agentSpan);
492
+
493
+ // Set active span context for log correlation
494
+ const spanCtx = agentSpan.spanContext();
495
+ const spanContext = trace.setSpan(context.active(), agentSpan);
496
+ context.with(spanContext, () => {
497
+ logger.emit({
498
+ severityNumber: SeverityNumber.INFO,
499
+ severityText: 'INFO',
500
+ body: `Agent Skill process started: ${processId}`,
501
+ attributes: {
502
+ // Explicit trace correlation (some backends need these)
503
+ trace_id: spanCtx.traceId,
504
+ span_id: spanCtx.spanId,
505
+ trace_flags: spanCtx.traceFlags,
506
+ agentId,
507
+ processId: agentProcessId,
508
+ input: agentInput,
509
+ body,
510
+ query,
511
+ headers,
512
+ },
513
+ } as any);
514
+ });
515
+ },
516
+ THook.NonBlocking
517
+ );
518
+
519
+ HookService.registerAfter(
520
+ 'SREAgent.process',
521
+ async function ({ result, error }) {
522
+ const agent = this.instance;
523
+ const agentProcessId = agent.agentRuntime.processID; // nested process has a subID that needs to be removed
524
+ const agentId = agent.id;
525
+ const _hookContext: any = this.context;
526
+
527
+ const ctx = OTelContextRegistry.get(agentId, agentProcessId);
528
+ if (!ctx) return;
529
+ const agentSpan = ctx.rootSpan;
530
+
531
+ if (!agentSpan) return;
532
+
533
+ if (error) {
534
+ agentSpan.recordException(error);
535
+ agentSpan.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
536
+ agentSpan.addEvent('skill.process.error', {
537
+ 'error.message': error.message,
538
+ });
539
+ } else {
540
+ agentSpan.setStatus({ code: SpanStatusCode.OK });
541
+ agentSpan.addEvent('skill.process.completed', {
542
+ 'output.size': JSON.stringify(result || {}).length,
543
+ });
544
+ agentSpan.setAttributes({
545
+ 'output.size': JSON.stringify(result || {}).length,
546
+ });
547
+ }
548
+
549
+ // Emit log BEFORE ending span to ensure context is active
550
+ const outputForLog = oTelInstance.formatOutputForLog(result, !!error);
551
+ const spanCtx = agentSpan.spanContext();
552
+ const logAttributes: Record<string, any> = {
553
+ // Explicit trace correlation (some backends need these)
554
+ trace_id: spanCtx.traceId,
555
+ span_id: spanCtx.spanId,
556
+ trace_flags: spanCtx.traceFlags,
557
+ agentId,
558
+ processId: agentProcessId,
559
+ hasError: !!error,
560
+ 'error.message': error?.message,
561
+ 'error.stack': error?.stack,
562
+ };
563
+
564
+ // Only include output if formatOutputForLog returns a value
565
+ if (outputForLog !== undefined) {
566
+ logAttributes['agent.output'] = outputForLog;
567
+ }
568
+
569
+ // Set active span context for log correlation
570
+ const spanContext = trace.setSpan(context.active(), agentSpan);
571
+ context.with(spanContext, () => {
572
+ logger.emit({
573
+ severityNumber: error ? SeverityNumber.ERROR : SeverityNumber.INFO,
574
+ severityText: error ? 'ERROR' : 'INFO',
575
+ body: `Agent process ${error ? 'failed' : 'completed'}: ${agentProcessId}`,
576
+ attributes: logAttributes,
577
+ } as any);
578
+ });
579
+
580
+ // End span after log is emitted
581
+ agentSpan.end();
582
+
583
+ OTelContextRegistry.endProcess(agentId, agentProcessId);
584
+ },
585
+ THook.NonBlocking
586
+ );
587
+
588
+ // In setupHooks() - Enhanced Component.process hook
589
+ HookService.register(
590
+ 'Component.process',
591
+ async function (input, settings, agent) {
592
+ const processId = agent.agentRuntime.processID;
593
+ const agentId = agent.id;
594
+ const component = this.instance; // Get the actual component instance
595
+ const componentId = settings.id || 'unknown';
596
+ const componentType = settings.name;
597
+ const componentName = settings.displayName || settings.name;
598
+ const eventId = settings.eventId; // specific event id attached to this component execution
599
+
600
+ const ctx = OTelContextRegistry.get(agentId, processId);
601
+ const parentSpan = ctx?.rootSpan;
602
+
603
+ const compSettingsData = oTelInstance.prepareComponentData(settings?.data || {}, 'cmp.settings');
604
+ const spanName = `Component.${componentType}`;
605
+ const span = tracer.startSpan(
606
+ spanName,
607
+ {
608
+ attributes: {
609
+ 'agent.id': agentId,
610
+ 'process.id': processId,
611
+ 'event.id': eventId,
612
+ 'cmp.id': componentId,
613
+ 'cmp.type': componentType,
614
+ 'cmp.name': componentName,
615
+ ...compSettingsData,
616
+ },
617
+ },
618
+ parentSpan ? trace.setSpan(context.active(), parentSpan) : undefined
619
+ );
620
+
621
+ // Add event: Component started
622
+ const inputStr = JSON.stringify(input || {});
623
+
624
+ const compInputData = oTelInstance.prepareComponentData(input || {});
625
+ span.addEvent('cmp.call', {
626
+ 'event.id': eventId,
627
+ 'cmp.input.size': JSON.stringify(input || {}).length,
628
+ 'cmp.input': JSON.stringify(compInputData),
629
+ });
630
+
631
+ // Emit structured log with full details
632
+ const spanContext = trace.setSpan(context.active(), span);
633
+ context.with(spanContext, () => {
634
+ logger.emit({
635
+ severityNumber: SeverityNumber.INFO,
636
+ severityText: 'INFO',
637
+ body: `Component ${componentType} started`,
638
+ attributes: {
639
+ 'agent.id': agentId,
640
+ 'process.id': processId,
641
+ 'event.id': eventId,
642
+ 'cmp.id': componentId,
643
+ 'cmp.type': componentType,
644
+ 'cmp.name': componentName,
645
+ 'cmp.input': input,
646
+ },
647
+ });
648
+ });
649
+
650
+ // Store span in hook context (isolated per component execution, concurrency-safe)
651
+ this.context.otelSpan = span;
652
+ },
653
+ THook.NonBlocking
654
+ );
655
+
656
+ HookService.registerAfter(
657
+ 'Component.process',
658
+ async function ({ result, error, args }) {
659
+ // Retrieve span from hook context (concurrency-safe)
660
+ const span = this.context.otelSpan;
661
+ if (!span) return;
662
+
663
+ const agent = args[2];
664
+ const settings = args[1];
665
+ const eventId = settings.eventId;
666
+ const processId = agent.agentRuntime.processID;
667
+ const agentId = agent.id;
668
+ const component = this.instance; // Get the actual component instance
669
+ const componentId = settings.id || 'unknown';
670
+ const componentType = settings.name;
671
+ const componentName = settings.displayName || settings.name;
672
+
673
+ if (error) {
674
+ // Capture error details
675
+ span.recordException(error);
676
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
677
+
678
+ // Add error event
679
+ span.addEvent('cmp.call.error', {
680
+ 'event.id': eventId,
681
+ 'cmp.id': componentId,
682
+ 'cmp.type': componentType,
683
+ 'cmp.name': componentName,
684
+ 'error.type': error.name,
685
+ 'error.message': error.message,
686
+ 'error.stack': error.stack?.substring(0, 500),
687
+ });
688
+
689
+ // Emit error log
690
+ const spanContext = trace.setSpan(context.active(), span);
691
+ context.with(spanContext, () => {
692
+ logger.emit({
693
+ severityNumber: SeverityNumber.ERROR,
694
+ severityText: 'ERROR',
695
+ body: `Component ${componentType} (${componentId}) failed: ${error.message}`,
696
+ attributes: {
697
+ 'agent.id': agentId,
698
+ 'process.id': processId,
699
+ 'event.id': eventId,
700
+ 'cmp.id': componentId,
701
+ 'cmp.name': componentName,
702
+ 'cmp.type': componentType,
703
+ 'error.type': error.name,
704
+ 'error.message': error.message,
705
+ 'error.stack': error.stack, // ← Full stack in logs
706
+ },
707
+ });
708
+ });
709
+ } else {
710
+ span.setStatus({ code: SpanStatusCode.OK });
711
+
712
+ // Add success event with output summary
713
+ const resultStr = JSON.stringify(result || {});
714
+ span.addEvent('cmp.call.result', {
715
+ 'output.size': resultStr.length,
716
+ 'output.preview': resultStr.substring(0, 200),
717
+ });
718
+
719
+ // Add output attributes to span
720
+ span.setAttributes({
721
+ 'output.size': JSON.stringify(result || {}).length,
722
+ 'output.has_error': !!result?._error,
723
+ });
724
+
725
+ // Emit success log with output (formatted safely)
726
+ const outputForLog = oTelInstance.formatOutputForLog(result, false);
727
+ const logAttributes: Record<string, any> = {
728
+ 'agent.id': agentId,
729
+ 'cmp.id': componentId,
730
+ 'cmp.type': componentType,
731
+ 'cmp.name': componentName,
732
+ 'process.id': processId,
733
+ 'event.id': eventId,
734
+ 'cmp.output': result,
735
+ };
736
+
737
+ // Only include output if formatOutputForLog returns a value
738
+ // if (outputForLog !== undefined) {
739
+ // logAttributes['cmp.output'] = outputForLog;
740
+ // }
741
+
742
+ const spanContext = trace.setSpan(context.active(), span);
743
+ context.with(spanContext, () => {
744
+ logger.emit({
745
+ severityNumber: SeverityNumber.INFO,
746
+ severityText: 'INFO',
747
+ body: `Component ${componentType} (${componentId}) completed successfully`,
748
+ attributes: logAttributes,
749
+ });
750
+ });
751
+ }
752
+
753
+ span.end();
754
+ },
755
+ THook.NonBlocking
756
+ );
757
+ return Promise.resolve();
758
+ }
759
+ }