@smythos/sre 1.7.41 → 1.8.0

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 (60) hide show
  1. package/CHANGELOG +136 -64
  2. package/dist/index.js +65 -50
  3. package/dist/index.js.map +1 -1
  4. package/dist/types/Components/Async.class.d.ts +11 -5
  5. package/dist/types/index.d.ts +2 -0
  6. package/dist/types/subsystems/AgentManager/AgentData.service/connectors/SQLiteAgentDataConnector.class.d.ts +45 -0
  7. package/dist/types/subsystems/LLMManager/LLM.helper.d.ts +32 -1
  8. package/dist/types/subsystems/LLMManager/LLM.inference.d.ts +25 -2
  9. package/dist/types/subsystems/LLMManager/LLM.service/connectors/Anthropic.class.d.ts +22 -2
  10. package/dist/types/subsystems/LLMManager/LLM.service/connectors/Bedrock.class.d.ts +2 -2
  11. package/dist/types/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.d.ts +27 -2
  12. package/dist/types/subsystems/LLMManager/LLM.service/connectors/Groq.class.d.ts +22 -2
  13. package/dist/types/subsystems/LLMManager/LLM.service/connectors/Ollama.class.d.ts +22 -2
  14. package/dist/types/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.d.ts +3 -3
  15. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.d.ts +23 -3
  16. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ChatCompletionsApiInterface.d.ts +2 -2
  17. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/OpenAIApiInterface.d.ts +2 -2
  18. package/dist/types/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.d.ts +2 -2
  19. package/dist/types/subsystems/LLMManager/LLM.service/connectors/xAI.class.d.ts +3 -3
  20. package/dist/types/subsystems/MemoryManager/LLMContext.d.ts +10 -3
  21. package/dist/types/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.class.d.ts +24 -0
  22. package/dist/types/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.redaction.helper.d.ts +49 -0
  23. package/dist/types/types/LLM.types.d.ts +30 -1
  24. package/package.json +4 -3
  25. package/src/Components/APICall/OAuth.helper.ts +16 -1
  26. package/src/Components/APIEndpoint.class.ts +11 -4
  27. package/src/Components/Async.class.ts +38 -5
  28. package/src/Components/GenAILLM.class.ts +13 -7
  29. package/src/Components/LLMAssistant.class.ts +3 -1
  30. package/src/Components/LogicAND.class.ts +13 -0
  31. package/src/Components/LogicAtLeast.class.ts +18 -0
  32. package/src/Components/LogicAtMost.class.ts +19 -0
  33. package/src/Components/LogicOR.class.ts +12 -2
  34. package/src/Components/LogicXOR.class.ts +11 -0
  35. package/src/constants.ts +1 -1
  36. package/src/helpers/Conversation.helper.ts +10 -8
  37. package/src/index.ts +2 -0
  38. package/src/index.ts.bak +2 -0
  39. package/src/subsystems/AgentManager/AgentData.service/connectors/SQLiteAgentDataConnector.class.ts +190 -0
  40. package/src/subsystems/AgentManager/AgentData.service/index.ts +2 -0
  41. package/src/subsystems/LLMManager/LLM.helper.ts +117 -1
  42. package/src/subsystems/LLMManager/LLM.inference.ts +136 -67
  43. package/src/subsystems/LLMManager/LLM.service/LLMConnector.ts +13 -6
  44. package/src/subsystems/LLMManager/LLM.service/connectors/Anthropic.class.ts +157 -33
  45. package/src/subsystems/LLMManager/LLM.service/connectors/Bedrock.class.ts +9 -8
  46. package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +121 -83
  47. package/src/subsystems/LLMManager/LLM.service/connectors/Groq.class.ts +125 -62
  48. package/src/subsystems/LLMManager/LLM.service/connectors/Ollama.class.ts +168 -76
  49. package/src/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.ts +18 -8
  50. package/src/subsystems/LLMManager/LLM.service/connectors/VertexAI.class.ts +8 -4
  51. package/src/subsystems/LLMManager/LLM.service/connectors/openai/OpenAIConnector.class.ts +50 -8
  52. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ChatCompletionsApiInterface.ts +30 -16
  53. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/OpenAIApiInterface.ts +2 -2
  54. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.ts +29 -15
  55. package/src/subsystems/LLMManager/LLM.service/connectors/xAI.class.ts +10 -8
  56. package/src/subsystems/MemoryManager/LLMContext.ts +27 -8
  57. package/src/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.class.ts +467 -120
  58. package/src/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.redaction.helper.ts +203 -0
  59. package/src/types/LLM.types.ts +31 -1
  60. package/src/types/node-sqlite.d.ts +45 -0
@@ -4,6 +4,7 @@ import { ACL } from '@sre/Security/AccessControl/ACL.class';
4
4
  import { IAccessCandidate } from '@sre/types/ACL.types';
5
5
  import { TelemetryConnector } from '../../TelemetryConnector';
6
6
  import { AgentCallLog } from '@sre/types/AgentLogger.types';
7
+ import { redactSensitiveString, redactData, redactHeaders } from './OTel.redaction.helper';
7
8
 
8
9
  import { trace, context, SpanStatusCode, Tracer, propagation } from '@opentelemetry/api';
9
10
  import { Logger as OTelLogger, logs, SeverityNumber } from '@opentelemetry/api-logs';
@@ -50,6 +51,14 @@ export type OTelLogConfig = {
50
51
  * These will be replaced with '[REDACTED]' in logs
51
52
  */
52
53
  redactFields?: string[];
54
+ /**
55
+ * Enable automatic redaction of sensitive data in logs and traces.
56
+ * When true (or omitted), sensitive data such as passwords, tokens,
57
+ * API keys, and JWT tokens are automatically replaced with '[REDACTED]'.
58
+ * Set to false to disable all automatic redaction.
59
+ * Default: true
60
+ */
61
+ enableRedaction?: boolean;
53
62
  };
54
63
  const OTEL_DEBUG_LOGS = true;
55
64
  export class OTel extends TelemetryConnector {
@@ -62,6 +71,9 @@ export class OTel extends TelemetryConnector {
62
71
 
63
72
  constructor(protected _settings: OTelLogConfig) {
64
73
  super();
74
+ // Default enableRedaction to true when not explicitly provided
75
+ _settings.enableRedaction = _settings.enableRedaction ?? true;
76
+
65
77
  if (!_settings.endpoint) {
66
78
  outputLogger.warn('OTel initialization skipped, endpoint is not set');
67
79
  return;
@@ -141,6 +153,7 @@ export class OTel extends TelemetryConnector {
141
153
  * Redact sensitive fields from an object
142
154
  */
143
155
  private redactSensitiveData(data: any, redactFields?: string[]): any {
156
+ if (!this._settings.enableRedaction) return data;
144
157
  if (!redactFields || redactFields.length === 0) return data;
145
158
  if (typeof data !== 'object' || data === null) return data;
146
159
 
@@ -157,6 +170,35 @@ export class OTel extends TelemetryConnector {
157
170
  return redacted;
158
171
  }
159
172
 
173
+ /**
174
+ * Redact sensitive patterns from a string value.
175
+ * Skips redaction when enableRedaction is explicitly set to false.
176
+ */
177
+ private redactString(value: string): string {
178
+ if (!this._settings.enableRedaction) return value;
179
+ return redactSensitiveString(value);
180
+ }
181
+
182
+ /**
183
+ * Redact sensitive data from any data type (objects, arrays, strings).
184
+ * Skips redaction when enableRedaction is explicitly set to false.
185
+ */
186
+ private redactObject<T>(data: T): T {
187
+ if (!this._settings.enableRedaction) return data;
188
+ return redactData(data);
189
+ }
190
+
191
+ /**
192
+ * Redact sensitive HTTP headers.
193
+ * Skips redaction when enableRedaction is explicitly set to false.
194
+ */
195
+ private redactRequestHeaders(
196
+ headers: Record<string, unknown> | string | undefined | null,
197
+ ): Record<string, unknown> | string | undefined | null {
198
+ if (!this._settings.enableRedaction) return headers;
199
+ return redactHeaders(headers);
200
+ }
201
+
160
202
  /**
161
203
  * Safely format output for logging with size limits and redaction
162
204
  */
@@ -170,11 +212,17 @@ export class OTel extends TelemetryConnector {
170
212
  return undefined;
171
213
  }
172
214
 
173
- // Redact sensitive fields
174
- const redacted = this.redactSensitiveData(output, config.redactFields);
215
+ // Redact sensitive fields (config-based)
216
+ let redacted = this.redactSensitiveData(output, config.redactFields);
217
+
218
+ // Apply SENSITIVE_WORDS-based redaction on the object (automatic key-based redaction)
219
+ redacted = this.redactObject(redacted);
175
220
 
176
221
  // Stringify
177
- const outputStr = JSON.stringify(redacted);
222
+ let outputStr = JSON.stringify(redacted);
223
+
224
+ // Apply string-based redaction on the stringified output to catch embedded JSON
225
+ outputStr = this.redactString(outputStr);
178
226
 
179
227
  // Check size limit
180
228
  if (outputStr && outputStr.length > maxSize) {
@@ -200,12 +248,38 @@ export class OTel extends TelemetryConnector {
200
248
  for (let key in data) {
201
249
  result[prefix ? `${prefix}.${key}` : key] = (typeof data[key] === 'object' ? JSON.stringify(data[key]) : data[key].toString()).substring(
202
250
  0,
203
- maxEntryLength
251
+ maxEntryLength,
204
252
  );
205
253
  }
206
254
 
207
255
  return result;
208
256
  }
257
+
258
+ private prepareContext(contextWindow: Array<{ role: string; content: string; [key: string]: unknown }>): string {
259
+ if (!contextWindow || !Array.isArray(contextWindow)) return '[]';
260
+
261
+ const filtered = contextWindow.filter((msg) => {
262
+ if (typeof msg !== 'object' || msg === null) return false;
263
+ const keys = Object.keys(msg);
264
+ return !keys.some((k) => k.includes('___smyth_metadata___'));
265
+ });
266
+
267
+ const lastAssistant = [...filtered].reverse().find((msg) => msg.role === 'assistant' && msg.content);
268
+ const lastUser = [...filtered].reverse().find((msg) => msg.role === 'user' && msg.content);
269
+
270
+ const messages: Array<{ role: string; content: string }> = [];
271
+ if (lastAssistant) {
272
+ const raw = typeof lastAssistant.content === 'string' ? lastAssistant.content : JSON.stringify(lastAssistant.content);
273
+ messages.push({ role: 'assistant', content: raw.substring(0, 2000) });
274
+ }
275
+ if (lastUser) {
276
+ const raw = typeof lastUser.content === 'string' ? lastUser.content : JSON.stringify(lastUser.content);
277
+ messages.push({ role: 'user', content: raw.substring(0, 2000) });
278
+ }
279
+
280
+ return JSON.stringify(messages);
281
+ }
282
+
209
283
  protected setupHooks(): Promise<void> {
210
284
  const tracer = this.tracer;
211
285
  const logger = this.logger;
@@ -219,15 +293,18 @@ export class OTel extends TelemetryConnector {
219
293
 
220
294
  const modelId = toolInfo.model;
221
295
  const contextWindow = toolInfo.contextWindow;
222
- const lastContext = contextWindow.filter((context) => context.role === 'user').slice(-2);
223
296
 
224
- const toolNames = toolInfo.map((tool) => tool.name + '(' + tool.arguments + ')');
297
+ const toolNames = toolInfo.map((tool) => {
298
+ const args = typeof tool.arguments === 'string' ? tool.arguments : JSON.stringify(tool.arguments);
299
+ return `${tool.name}(${args})`;
300
+ });
225
301
  hookContext.curLLMGenSpan.addEvent('llm.gen.tool.calls', {
226
- 'tool.calls': toolNames.join(', '),
227
- 'llm.model': modelId || 'unknown',
228
- 'context.preview': JSON.stringify(lastContext).substring(0, 200),
302
+ 'tool.calls': oTelInstance.redactString(toolNames.join(', ')),
303
+ 'llm.model': modelId || '',
304
+ 'context.preview': oTelInstance.redactString(oTelInstance.prepareContext(contextWindow).substring(0, 200)),
229
305
  });
230
306
 
307
+ const llmSpanCtx = hookContext.curLLMGenSpan.spanContext();
231
308
  const spanContext = trace.setSpan(context.active(), hookContext.curLLMGenSpan);
232
309
  context.with(spanContext, () => {
233
310
  logger.emit({
@@ -235,10 +312,15 @@ export class OTel extends TelemetryConnector {
235
312
  severityText: 'INFO',
236
313
  body: `LLM tool calls: ${toolNames.join(', ')}`,
237
314
  attributes: {
315
+ // Explicit trace correlation (some backends need these)
316
+ trace_id: llmSpanCtx.traceId,
317
+ span_id: llmSpanCtx.spanId,
318
+ trace_flags: llmSpanCtx.traceFlags,
319
+
238
320
  'agent.id': hookContext.agentId,
239
321
  'conv.id': hookContext.processId,
240
- 'llm.model': modelId || 'unknown',
241
- 'context.preview': JSON.stringify(lastContext).substring(0, 5000),
322
+ 'llm.model': modelId || '',
323
+ 'context.preview': oTelInstance.redactString(oTelInstance.prepareContext(contextWindow)),
242
324
  },
243
325
  });
244
326
  });
@@ -259,7 +341,6 @@ export class OTel extends TelemetryConnector {
259
341
  const modelId = reqInfo.model;
260
342
  const contextWindow = reqInfo.contextWindow;
261
343
 
262
- const lastContext = contextWindow.filter((context) => context.role === 'user').slice(-2);
263
344
  // End TTFB span when first data arrives
264
345
  if (hookContext?.latencySpans?.[reqInfo.requestId]) {
265
346
  const ttfbSpan = hookContext.latencySpans[reqInfo.requestId];
@@ -268,7 +349,7 @@ export class OTel extends TelemetryConnector {
268
349
  ttfbSpan.addEvent('llm.first.byte.received', {
269
350
  'request.id': reqInfo.requestId,
270
351
  'data.size': JSON.stringify(data || {}).length,
271
- 'llm.model': modelId || 'unknown',
352
+ 'llm.model': modelId || '',
272
353
  });
273
354
 
274
355
  ttfbSpan.setStatus({ code: SpanStatusCode.OK });
@@ -284,22 +365,98 @@ export class OTel extends TelemetryConnector {
284
365
  'agent.id': hookContext.agentId,
285
366
  'conv.id': hookContext.processId,
286
367
  'team.id': hookContext.teamId,
287
- 'llm.model': modelId || 'unknown',
368
+ 'llm.model': modelId || '',
288
369
  },
289
370
  },
290
- trace.setSpan(context.active(), hookContext.convSpan)
371
+ trace.setSpan(context.active(), hookContext.convSpan),
291
372
  );
292
373
  llmGenSpan.addEvent('llm.gen.started', {
293
374
  'request.id': reqInfo.requestId,
294
375
  timestamp: Date.now(),
295
- 'llm.model': modelId || 'unknown',
296
- 'context.preview': JSON.stringify(lastContext).substring(0, 200),
376
+ 'llm.model': modelId || '',
377
+ 'context.preview': oTelInstance.redactString(oTelInstance.prepareContext(contextWindow).substring(0, 200)),
378
+ });
379
+
380
+ const llmGenSpanCtx = llmGenSpan.spanContext();
381
+ const llmGenSpanContext = trace.setSpan(context.active(), llmGenSpan);
382
+ context.with(llmGenSpanContext, () => {
383
+ logger.emit({
384
+ severityNumber: SeverityNumber.INFO,
385
+ severityText: 'INFO',
386
+ body: `LLM generation started: ${hookContext.processId}`,
387
+ attributes: {
388
+ // Explicit trace correlation (some backends need these)
389
+ trace_id: llmGenSpanCtx.traceId,
390
+ span_id: llmGenSpanCtx.spanId,
391
+ trace_flags: llmGenSpanCtx.traceFlags,
392
+
393
+ 'agent.id': hookContext.agentId,
394
+ 'conv.id': hookContext.processId,
395
+ 'team.id': hookContext.teamId,
396
+ 'llm.model': modelId || '',
397
+ 'request.id': reqInfo.requestId,
398
+ 'context.preview': oTelInstance.redactString(oTelInstance.prepareContext(contextWindow)),
399
+ },
400
+ });
297
401
  });
402
+
298
403
  hookContext.curLLMGenSpan = llmGenSpan;
299
404
  if (OTEL_DEBUG_LOGS) outputLogger.debug('createDataHandler completed', reqInfo?.requestId, accessCandidate);
300
405
  };
301
406
  };
302
407
 
408
+ const createErrorHandler = function (hookContext: any) {
409
+ return function (error: Error, metadata?: { requestId?: string }) {
410
+ if (!hookContext.convSpan) return;
411
+ const accessCandidate = AccessCandidate.agent(hookContext?.agentId);
412
+ if (OTEL_DEBUG_LOGS)
413
+ outputLogger.debug('Error event received', { error: error?.message, requestId: metadata?.requestId }, accessCandidate);
414
+
415
+ // Mark that an error occurred so after hook knows not to log success
416
+ hookContext.hasError = true;
417
+ hookContext.errorDetails = error;
418
+
419
+ const convSpan = hookContext.convSpan;
420
+ const spanCtx = convSpan.spanContext();
421
+ const spanContext = trace.setSpan(context.active(), convSpan);
422
+
423
+ // Record exception on span
424
+ convSpan.recordException(error);
425
+ convSpan.setStatus({ code: SpanStatusCode.ERROR, message: error?.message || 'Unknown error' });
426
+ convSpan.addEvent('conv.error', {
427
+ 'error.message': error?.message || 'Unknown error',
428
+ 'request.id': metadata?.requestId || 'unknown',
429
+ });
430
+
431
+ context.with(spanContext, () => {
432
+ logger.emit({
433
+ severityNumber: SeverityNumber.ERROR,
434
+ severityText: 'ERROR',
435
+ body: `Conversation error: ${hookContext.processId}`,
436
+ attributes: {
437
+ // Explicit trace correlation (some backends need these)
438
+ trace_id: spanCtx.traceId,
439
+ span_id: spanCtx.spanId,
440
+ trace_flags: spanCtx.traceFlags,
441
+
442
+ 'agent.id': hookContext.agentId,
443
+ 'agent.name': hookContext.agentName,
444
+ 'conv.id': hookContext.processId,
445
+ 'error.message': error?.message || 'Unknown error',
446
+ 'error.stack': error?.stack,
447
+ 'team.id': hookContext.teamId,
448
+ 'org.slot': hookContext.orgSlot,
449
+ 'agent.debug': hookContext.isDebugSession,
450
+ 'agent.isTest': hookContext.isTestDomain,
451
+ 'request.id': metadata?.requestId || 'unknown',
452
+ },
453
+ });
454
+ });
455
+
456
+ if (OTEL_DEBUG_LOGS) outputLogger.debug('Error event handled', { error: error?.message }, accessCandidate);
457
+ };
458
+ };
459
+
303
460
  const createRequestedHandler = function (hookContext) {
304
461
  return function (reqInfo: any) {
305
462
  if (!hookContext.convSpan) return;
@@ -308,8 +465,6 @@ export class OTel extends TelemetryConnector {
308
465
  if (!hookContext.latencySpans) hookContext.latencySpans = {};
309
466
  const contextWindow = reqInfo.contextWindow;
310
467
 
311
- const lastContext = contextWindow.filter((context) => context.role === 'user').slice(-2);
312
-
313
468
  const modelId = reqInfo.model;
314
469
  const llmGenLatencySpan = tracer.startSpan(
315
470
  'Conv.GenAI.TTFB',
@@ -319,16 +474,16 @@ export class OTel extends TelemetryConnector {
319
474
  'conv.id': hookContext.processId,
320
475
  'team.id': hookContext.teamId,
321
476
  'request.id': reqInfo.requestId,
322
- 'llm.model': modelId || 'unknown',
477
+ 'llm.model': modelId || '',
323
478
  'metric.type': 'ttfb',
324
479
  },
325
480
  },
326
- trace.setSpan(context.active(), hookContext.convSpan)
481
+ trace.setSpan(context.active(), hookContext.convSpan),
327
482
  );
328
483
  llmGenLatencySpan.addEvent('llm.requested', {
329
484
  'request.id': reqInfo.requestId,
330
485
  timestamp: Date.now(),
331
- 'context.preview': JSON.stringify(lastContext).substring(0, 200),
486
+ 'context.preview': oTelInstance.redactString(oTelInstance.prepareContext(contextWindow).substring(0, 200)),
332
487
  });
333
488
  hookContext.latencySpans[reqInfo.requestId] = llmGenLatencySpan;
334
489
  if (OTEL_DEBUG_LOGS) outputLogger.debug('createRequestedHandler completed', reqInfo?.requestId, accessCandidate);
@@ -351,6 +506,7 @@ export class OTel extends TelemetryConnector {
351
506
  const sessionId = processId;
352
507
  const workflowId = agentData?.workflowReqId || agentData?.workflowID || agentData?.workflowId || undefined;
353
508
  const logTags = agentData?.sessionTag || (isDebugSession ? 'DEBUG' : undefined);
509
+ const agentName = agentData?.name || undefined;
354
510
 
355
511
  if (message == null) {
356
512
  //this is a conversation step, will be handled by createRequestedHandler
@@ -366,16 +522,17 @@ export class OTel extends TelemetryConnector {
366
522
  attributes: {
367
523
  // OTel standard attributes
368
524
  'gen_ai.operation.name': 'chat',
369
- 'gen_ai.provider.name': conversation?.llmInference?.llmProviderName || 'unknown',
525
+ 'gen_ai.provider.name': conversation?.llmInference?.llmProviderName || '',
370
526
  'gen_ai.conversation.id': processId,
371
- 'gen_ai.request.model': modelId || 'unknown',
527
+ 'gen_ai.request.model': modelId || '',
372
528
  ////////////////////////////////
373
529
  'team.id': teamId,
374
530
  'org.tier': orgTier,
375
531
  'org.slot': orgSlot,
376
532
  'agent.id': agentId,
533
+ 'agent.name': agentName,
377
534
  'conv.id': processId,
378
- 'llm.model': modelId || 'unknown',
535
+ 'llm.model': modelId || '',
379
536
  'agent.debug': isDebugSession,
380
537
  'agent.isTest': isTestDomain,
381
538
  'session.id': sessionId,
@@ -384,6 +541,7 @@ export class OTel extends TelemetryConnector {
384
541
  });
385
542
  hookContext.convSpan = convSpan;
386
543
  hookContext.agentId = agentId;
544
+ hookContext.agentName = agentData?.name || undefined;
387
545
  hookContext.processId = processId;
388
546
  hookContext.teamId = teamId;
389
547
  hookContext.orgSlot = orgSlot;
@@ -410,12 +568,15 @@ export class OTel extends TelemetryConnector {
410
568
  hookContext.toolInfoHandler = createToolInfoHandler(hookContext);
411
569
  conversation.on(TLLMEvent.ToolInfo, hookContext.toolInfoHandler);
412
570
 
571
+ hookContext.errorHandler = createErrorHandler(hookContext);
572
+ conversation.on(TLLMEvent.Error, hookContext.errorHandler);
573
+
413
574
  // Add start event
414
575
 
415
576
  convSpan.addEvent('skill.process.started', {
416
577
  'input.size': JSON.stringify(message || {}).length,
417
- 'input.preview': message.substring(0, 200),
418
- 'llm.model': modelId || 'unknown',
578
+ 'input.preview': oTelInstance.redactString(message.substring(0, 200)),
579
+ 'llm.model': modelId || '',
419
580
  });
420
581
 
421
582
  OTelContextRegistry.startProcess(agentId, processId, convSpan);
@@ -438,9 +599,10 @@ export class OTel extends TelemetryConnector {
438
599
  'org.slot': orgSlot,
439
600
 
440
601
  'agent.id': agentId,
602
+ 'agent.name': agentName,
441
603
  'conv.id': processId,
442
604
  'input.size': JSON.stringify(message || {}).length,
443
- 'input.preview': message.substring(0, 2000),
605
+ 'input.preview': oTelInstance.redactString(message.substring(0, 4000)),
444
606
  'agent.debug': isDebugSession,
445
607
  'agent.isTest': isTestDomain,
446
608
  'session.id': sessionId,
@@ -450,7 +612,7 @@ export class OTel extends TelemetryConnector {
450
612
  });
451
613
  });
452
614
  },
453
- THook.NonBlocking
615
+ THook.NonBlocking,
454
616
  );
455
617
 
456
618
  HookService.registerAfter(
@@ -471,6 +633,7 @@ export class OTel extends TelemetryConnector {
471
633
  const sessionId = processId;
472
634
  const workflowId = agentData?.workflowReqId || agentData?.workflowID || agentData?.workflowId || undefined;
473
635
  const logTags = agentData?.sessionTag || (isDebugSession ? 'DEBUG' : undefined);
636
+ const agentName = agentData?.name || undefined;
474
637
 
475
638
  if (message == null) {
476
639
  return;
@@ -482,10 +645,16 @@ export class OTel extends TelemetryConnector {
482
645
  const accessCandidate = AccessCandidate.agent(agentId);
483
646
  if (OTEL_DEBUG_LOGS) outputLogger.debug('Conversation.streamPrompt completed', { processId }, accessCandidate);
484
647
 
648
+ // Handle curLLMGenSpan with error awareness
485
649
  if (hookContext.curLLMGenSpan) {
650
+ if (error) {
651
+ hookContext.curLLMGenSpan.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
652
+ }
486
653
  hookContext.curLLMGenSpan.addEvent('llm.gen.content', {
487
654
  'content.size': JSON.stringify(result || {}).length,
488
- 'content.preview': result.substring(0, 200),
655
+ 'content.preview': oTelInstance.redactString(
656
+ typeof result === 'string' ? result.substring(0, 200) : JSON.stringify(result || {}).substring(0, 200),
657
+ ),
489
658
  });
490
659
  hookContext.curLLMGenSpan.end();
491
660
 
@@ -494,37 +663,92 @@ export class OTel extends TelemetryConnector {
494
663
  if (hookContext.requestedHandler) conversation.off(TLLMEvent.Requested, hookContext.requestedHandler);
495
664
  }
496
665
 
666
+ if (hookContext.errorHandler) conversation.off(TLLMEvent.Error, hookContext.errorHandler);
667
+
497
668
  const { rootSpan: convSpan } = ctx;
498
669
 
499
670
  const spanCtx = convSpan.spanContext();
500
671
  const spanContext = trace.setSpan(context.active(), convSpan);
501
- context.with(spanContext, () => {
502
- logger.emit({
503
- severityNumber: SeverityNumber.INFO,
504
- severityText: 'INFO',
505
- body: `Conversation.streamPrompt completed: ${processId}`,
506
- attributes: {
507
- 'agent.id': agentId,
508
- 'conv.id': processId,
509
- 'output.size': JSON.stringify(result || {}).length,
510
- 'output.preview': result.substring(0, 2000),
511
- 'team.id': teamId,
512
- 'org.tier': orgTier,
513
- 'org.slot': orgSlot,
514
- 'agent.debug': isDebugSession,
515
- 'agent.isTest': isTestDomain,
516
- 'session.id': sessionId,
517
- 'workflow.id': workflowId,
518
- 'log.tags': logTags,
519
- },
672
+
673
+ // Check for errors - either thrown (error param) or emitted via event (hookContext.hasError)
674
+ const hasError = error || hookContext.hasError;
675
+
676
+ if (hasError) {
677
+ if (error && !hookContext.hasError) {
678
+ convSpan.recordException(error);
679
+ convSpan.setStatus({ code: SpanStatusCode.ERROR, message: error.message || 'Unknown error' });
680
+ convSpan.addEvent('conv.error', {
681
+ 'error.message': error.message || 'Unknown error',
682
+ });
683
+
684
+ context.with(spanContext, () => {
685
+ logger.emit({
686
+ severityNumber: SeverityNumber.ERROR,
687
+ severityText: 'ERROR',
688
+ body: `Conversation.streamPrompt failed: ${processId}`,
689
+ attributes: {
690
+ // Explicit trace correlation (some backends need these)
691
+ trace_id: spanCtx.traceId,
692
+ span_id: spanCtx.spanId,
693
+ trace_flags: spanCtx.traceFlags,
694
+
695
+ 'agent.id': agentId,
696
+ 'agent.name': agentName,
697
+ 'conv.id': processId,
698
+ 'error.message': error.message || 'Unknown error',
699
+ 'error.stack': error.stack,
700
+ 'team.id': teamId,
701
+ 'org.tier': orgTier,
702
+ 'org.slot': orgSlot,
703
+ 'agent.debug': isDebugSession,
704
+ 'agent.isTest': isTestDomain,
705
+ 'session.id': sessionId,
706
+ 'workflow.id': workflowId,
707
+ 'log.tags': logTags,
708
+ },
709
+ });
710
+ });
711
+ }
712
+ } else {
713
+ // Success handling
714
+ convSpan.setStatus({ code: SpanStatusCode.OK });
715
+
716
+ context.with(spanContext, () => {
717
+ logger.emit({
718
+ severityNumber: SeverityNumber.INFO,
719
+ severityText: 'INFO',
720
+ body: `Conversation.streamPrompt completed: ${processId}`,
721
+ attributes: {
722
+ // Explicit trace correlation (some backends need these)
723
+ trace_id: spanCtx.traceId,
724
+ span_id: spanCtx.spanId,
725
+ trace_flags: spanCtx.traceFlags,
726
+
727
+ 'agent.id': agentId,
728
+ 'agent.name': agentName,
729
+ 'conv.id': processId,
730
+ 'output.size': JSON.stringify(result || {}).length,
731
+ 'output.preview': oTelInstance.redactString(
732
+ (typeof result === 'string' ? result : JSON.stringify(result || {})).substring(0, 4000),
733
+ ),
734
+ 'team.id': teamId,
735
+ 'org.tier': orgTier,
736
+ 'org.slot': orgSlot,
737
+ 'agent.debug': isDebugSession,
738
+ 'agent.isTest': isTestDomain,
739
+ 'session.id': sessionId,
740
+ 'workflow.id': workflowId,
741
+ 'log.tags': logTags,
742
+ },
743
+ });
520
744
  });
521
- });
745
+ }
522
746
 
523
747
  convSpan.end();
524
748
 
525
749
  OTelContextRegistry.endProcess(agentId, processId);
526
750
  },
527
- THook.NonBlocking
751
+ THook.NonBlocking,
528
752
  );
529
753
 
530
754
  HookService.register(
@@ -551,6 +775,7 @@ export class OTel extends TelemetryConnector {
551
775
  const logTags = agent.sessionTag || (isDebugSession ? 'DEBUG' : undefined);
552
776
  const isTestDomain = agent.usingTestDomain || false;
553
777
  const domain = agent.domain || undefined;
778
+ const agentName = agent.name || undefined;
554
779
 
555
780
  const accessCandidate = AccessCandidate.agent(agentId);
556
781
  if (OTEL_DEBUG_LOGS) outputLogger.debug('SREAgent.process started', { processId, agentProcessId, endpointPath }, accessCandidate);
@@ -562,6 +787,11 @@ export class OTel extends TelemetryConnector {
562
787
 
563
788
  const input = { body, query, headers, processInput: agentInput };
564
789
 
790
+ const logBody = oTelInstance.prepareComponentData(agentRequest.body || {}, undefined, 4000);
791
+ const logQuery = oTelInstance.prepareComponentData(agentRequest.query || {}, undefined, 4000);
792
+ const logHeaders = oTelInstance.prepareComponentData(agentRequest.headers || {}, undefined, 4000);
793
+ const logAgentInput = oTelInstance.prepareComponentData(inputData || {}, undefined, 4000);
794
+
565
795
  let convSpan;
566
796
  let parentContext = context.active();
567
797
 
@@ -594,6 +824,7 @@ export class OTel extends TelemetryConnector {
594
824
  {
595
825
  attributes: {
596
826
  'agent.id': agentId,
827
+ 'agent.name': agentName,
597
828
  'team.id': teamId,
598
829
  'conv.id': conversationId,
599
830
  'process.id': agentProcessId,
@@ -606,11 +837,11 @@ export class OTel extends TelemetryConnector {
606
837
  'agent.domain': domain,
607
838
  },
608
839
  },
609
- parentContext
840
+ parentContext,
610
841
  );
611
842
 
612
843
  // Add start event
613
- const inputPreview = JSON.stringify(input || {}).substring(0, 200);
844
+ const inputPreview = oTelInstance.redactString(JSON.stringify(input || {}).substring(0, 200));
614
845
  agentSpan.addEvent('skill.process.started', {
615
846
  endpoint: endpointPath,
616
847
  'input.size': JSON.stringify(input || {}).length,
@@ -632,12 +863,14 @@ export class OTel extends TelemetryConnector {
632
863
  trace_id: spanCtx.traceId,
633
864
  span_id: spanCtx.spanId,
634
865
  trace_flags: spanCtx.traceFlags,
866
+
635
867
  'agent.id': agentId,
868
+ 'agent.name': agentName,
636
869
  'process.id': agentProcessId,
637
- input: agentInput,
638
- body,
639
- query,
640
- headers,
870
+ input: oTelInstance.redactObject(logAgentInput),
871
+ body: oTelInstance.redactObject(logBody),
872
+ query: oTelInstance.redactObject(logQuery),
873
+ headers: oTelInstance.redactRequestHeaders(logHeaders),
641
874
  'team.id': teamId,
642
875
  'org.slot': orgSlot,
643
876
  'org.tier': orgTier,
@@ -652,7 +885,7 @@ export class OTel extends TelemetryConnector {
652
885
  } as any);
653
886
  });
654
887
  },
655
- THook.NonBlocking
888
+ THook.NonBlocking,
656
889
  );
657
890
 
658
891
  HookService.registerAfter(
@@ -674,6 +907,7 @@ export class OTel extends TelemetryConnector {
674
907
  const logTags = agent.sessionTag || (isDebugSession ? 'DEBUG' : undefined);
675
908
  const isTestDomain = agent.usingTestDomain || false;
676
909
  const domain = agent.domain || undefined;
910
+ const agentName = agent.name || undefined;
677
911
 
678
912
  const ctx = OTelContextRegistry.get(agentId, agentProcessId);
679
913
  if (!ctx) return;
@@ -684,12 +918,32 @@ export class OTel extends TelemetryConnector {
684
918
  const accessCandidate = AccessCandidate.agent(agentId);
685
919
  if (OTEL_DEBUG_LOGS) outputLogger.debug('SREAgent.process completed', { agentProcessId }, accessCandidate);
686
920
 
921
+ // Check for error indicators in result (process returned error without throwing)
922
+ const hasResultError = !error && (!!result?._error || !!result?.error);
923
+ const resultError = hasResultError ? result._error || result.error : null;
924
+ const resultErrorMessage = resultError?.message || (typeof resultError === 'string' ? resultError : null);
925
+
926
+ // Determine if this is an error case (either thrown error or result error)
927
+ const isError = !!error || hasResultError;
928
+ const errorMessage = error?.message || resultErrorMessage || 'Process returned error';
929
+
687
930
  if (error) {
688
931
  agentSpan.recordException(error);
689
932
  agentSpan.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
690
933
  agentSpan.addEvent('skill.process.error', {
691
934
  'error.message': error.message,
692
935
  });
936
+ } else if (hasResultError) {
937
+ // Handle error in result (no exception thrown)
938
+ agentSpan.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });
939
+ agentSpan.addEvent('skill.process.error', {
940
+ 'error.message': errorMessage,
941
+ 'error.type': 'result_error',
942
+ });
943
+ agentSpan.setAttributes({
944
+ 'output.size': JSON.stringify(result || {}).length,
945
+ 'output.has_error': true,
946
+ });
693
947
  } else {
694
948
  agentSpan.setStatus({ code: SpanStatusCode.OK });
695
949
  agentSpan.addEvent('skill.process.completed', {
@@ -701,18 +955,21 @@ export class OTel extends TelemetryConnector {
701
955
  }
702
956
 
703
957
  // Emit log BEFORE ending span to ensure context is active
704
- const outputForLog = oTelInstance.formatOutputForLog(result, !!error);
958
+ const outputForLog = oTelInstance.formatOutputForLog(result, isError);
705
959
  const spanCtx = agentSpan.spanContext();
706
960
  const logAttributes: Record<string, any> = {
707
961
  // Explicit trace correlation (some backends need these)
708
962
  trace_id: spanCtx.traceId,
709
963
  span_id: spanCtx.spanId,
710
964
  trace_flags: spanCtx.traceFlags,
965
+
711
966
  'agent.id': agentId,
967
+ 'agent.name': agentName,
712
968
  'process.id': agentProcessId,
713
- hasError: !!error,
714
- 'error.message': error?.message,
969
+ hasError: isError,
970
+ 'error.message': isError ? errorMessage : undefined,
715
971
  'error.stack': error?.stack,
972
+ 'error.type': hasResultError ? 'result_error' : undefined,
716
973
  'team.id': teamId,
717
974
  'org.slot': orgSlot,
718
975
  'org.tier': orgTier,
@@ -734,9 +991,9 @@ export class OTel extends TelemetryConnector {
734
991
  const spanContext = trace.setSpan(context.active(), agentSpan);
735
992
  context.with(spanContext, () => {
736
993
  logger.emit({
737
- severityNumber: error ? SeverityNumber.ERROR : SeverityNumber.INFO,
738
- severityText: error ? 'ERROR' : 'INFO',
739
- body: `Agent process ${error ? 'failed' : 'completed'}: ${agentProcessId}`,
994
+ severityNumber: isError ? SeverityNumber.ERROR : SeverityNumber.INFO,
995
+ severityText: isError ? 'ERROR' : 'INFO',
996
+ body: `Agent process ${isError ? 'failed' : 'completed'}: ${agentProcessId}`,
740
997
  attributes: logAttributes,
741
998
  } as any);
742
999
  });
@@ -746,7 +1003,7 @@ export class OTel extends TelemetryConnector {
746
1003
 
747
1004
  OTelContextRegistry.endProcess(agentId, agentProcessId);
748
1005
  },
749
- THook.NonBlocking
1006
+ THook.NonBlocking,
750
1007
  );
751
1008
 
752
1009
  // In setupHooks() - Enhanced Component.process hook
@@ -777,6 +1034,7 @@ export class OTel extends TelemetryConnector {
777
1034
  const isDebugSession = agent.debugSessionEnabled || agent.agentRuntime?.debug || false;
778
1035
  const logTags = agent.sessionTag || (isDebugSession ? 'DEBUG' : undefined);
779
1036
  const isTestDomain = agent.usingTestDomain || false;
1037
+ const agentName = agent.name || undefined;
780
1038
 
781
1039
  const inputAction = input?.__action || undefined;
782
1040
  const inputStatus = input?.__status || undefined;
@@ -793,6 +1051,7 @@ export class OTel extends TelemetryConnector {
793
1051
  {
794
1052
  attributes: {
795
1053
  'agent.id': agentId,
1054
+ 'agent.name': agentName,
796
1055
  'process.id': processId,
797
1056
  'event.id': eventId,
798
1057
  'cmp.id': componentId,
@@ -811,22 +1070,30 @@ export class OTel extends TelemetryConnector {
811
1070
  ...compSettingsData,
812
1071
  },
813
1072
  },
814
- parentSpan ? trace.setSpan(context.active(), parentSpan) : undefined
1073
+ parentSpan ? trace.setSpan(context.active(), parentSpan) : undefined,
815
1074
  );
816
1075
 
817
1076
  // Add event: Component started - includes input.action and input.status for workflow tracking
818
- const inputStr = JSON.stringify(input || {});
819
-
820
- const compInputData = oTelInstance.prepareComponentData(input || {});
1077
+ // Use component-specific input (from predecessor nodes), not the merged object with agent variables
1078
+ // For APIEndpoint, use HTTP request body/query as the actual user input
1079
+ const componentInput =
1080
+ componentType === 'APIEndpoint'
1081
+ ? agent.agentRequest?.method === 'GET'
1082
+ ? agent.agentRequest?.query
1083
+ : agent.agentRequest?.body
1084
+ : componentData?.runtimeData?.input || {};
1085
+
1086
+ const compInputData = oTelInstance.prepareComponentData(componentInput || {});
821
1087
  span.addEvent('cmp.call', {
822
1088
  'event.id': eventId,
823
- 'cmp.input.size': JSON.stringify(input || {}).length,
824
- 'cmp.input': JSON.stringify(compInputData),
1089
+ 'cmp.input.size': JSON.stringify(componentInput || {}).length,
1090
+ 'cmp.input': oTelInstance.redactString(JSON.stringify(compInputData)),
825
1091
  'input.action': inputAction,
826
1092
  'input.status': inputStatus,
827
1093
  });
828
1094
 
829
1095
  // Emit structured log with full details
1096
+ const cmpSpanCtx = span.spanContext();
830
1097
  const spanContext = trace.setSpan(context.active(), span);
831
1098
  context.with(spanContext, () => {
832
1099
  logger.emit({
@@ -834,13 +1101,19 @@ export class OTel extends TelemetryConnector {
834
1101
  severityText: 'INFO',
835
1102
  body: `Component ${componentType} started`,
836
1103
  attributes: {
1104
+ // Explicit trace correlation (some backends need these)
1105
+ trace_id: cmpSpanCtx.traceId,
1106
+ span_id: cmpSpanCtx.spanId,
1107
+ trace_flags: cmpSpanCtx.traceFlags,
1108
+
837
1109
  'agent.id': agentId,
1110
+ 'agent.name': agentName,
838
1111
  'process.id': processId,
839
1112
  'event.id': eventId,
840
1113
  'cmp.id': componentId,
841
1114
  'cmp.type': componentType,
842
1115
  'cmp.name': componentName,
843
- 'cmp.input': input,
1116
+ 'cmp.input': oTelInstance.redactObject(componentInput),
844
1117
  'team.id': teamId,
845
1118
  'org.slot': orgSlot,
846
1119
  'org.tier': orgTier,
@@ -859,7 +1132,7 @@ export class OTel extends TelemetryConnector {
859
1132
  // Store span in hook context (isolated per component execution, concurrency-safe)
860
1133
  this.context.otelSpan = span;
861
1134
  },
862
- THook.NonBlocking
1135
+ THook.NonBlocking,
863
1136
  );
864
1137
 
865
1138
  HookService.registerAfter(
@@ -894,6 +1167,7 @@ export class OTel extends TelemetryConnector {
894
1167
  const isDebugSession = agent.debugSessionEnabled || agent.agentRuntime?.debug || false;
895
1168
  const logTags = agent.sessionTag || (isDebugSession ? 'DEBUG' : undefined);
896
1169
  const isTestDomain = agent.usingTestDomain || false;
1170
+ const agentName = agent.name || undefined;
897
1171
 
898
1172
  const accessCandidate = AccessCandidate.agent(agentId);
899
1173
  if (OTEL_DEBUG_LOGS) outputLogger.debug('Component.process completed', { componentId }, accessCandidate);
@@ -915,6 +1189,7 @@ export class OTel extends TelemetryConnector {
915
1189
  });
916
1190
 
917
1191
  // Emit error log
1192
+ const cmpErrorSpanCtx = span.spanContext();
918
1193
  const spanContext = trace.setSpan(context.active(), span);
919
1194
  context.with(spanContext, () => {
920
1195
  logger.emit({
@@ -922,7 +1197,13 @@ export class OTel extends TelemetryConnector {
922
1197
  severityText: 'ERROR',
923
1198
  body: `Component ${componentType} (${componentId}) failed: ${error.message}`,
924
1199
  attributes: {
1200
+ // Explicit trace correlation (some backends need these)
1201
+ trace_id: cmpErrorSpanCtx.traceId,
1202
+ span_id: cmpErrorSpanCtx.spanId,
1203
+ trace_flags: cmpErrorSpanCtx.traceFlags,
1204
+
925
1205
  'agent.id': agentId,
1206
+ 'agent.name': agentName,
926
1207
  'process.id': processId,
927
1208
  'event.id': eventId,
928
1209
  'cmp.id': componentId,
@@ -946,63 +1227,129 @@ export class OTel extends TelemetryConnector {
946
1227
  });
947
1228
  });
948
1229
  } else {
949
- span.setStatus({ code: SpanStatusCode.OK });
950
-
951
- // Add success event with output summary
1230
+ // Check if result contains an error indicator (component returned error without throwing)
1231
+ const hasResultError = !!result?._error || !!result?.error;
952
1232
  const resultStr = JSON.stringify(result || {});
953
- span.addEvent('cmp.call.result', {
954
- 'output.size': resultStr.length,
955
- 'output.preview': resultStr.substring(0, 200),
956
- });
957
1233
 
958
- // Add output attributes to span
959
- span.setAttributes({
960
- 'output.size': JSON.stringify(result || {}).length,
961
- 'output.has_error': !!result?._error,
962
- });
1234
+ if (hasResultError) {
1235
+ // Treat as error even though no exception was thrown
1236
+ const resultError = result._error || result.error;
1237
+ const errorMessage = resultError?.message || (typeof resultError === 'string' ? resultError : 'Component returned error');
963
1238
 
964
- // Emit success log with output (formatted safely)
965
- const outputForLog = oTelInstance.formatOutputForLog(result, false);
966
- const logAttributes: Record<string, any> = {
967
- 'agent.id': agentId,
968
- 'cmp.id': componentId,
969
- 'cmp.type': componentType,
970
- 'cmp.name': componentName,
971
- 'process.id': processId,
972
- 'event.id': eventId,
973
- 'cmp.output': result,
974
- 'team.id': teamId,
975
- 'org.slot': orgSlot,
976
- 'org.tier': orgTier,
977
- 'source.id': sourceId,
978
- 'source.name': sourceName,
979
- 'session.id': sessionId,
980
- 'workflow.id': workflowId,
981
- 'workflow.step': workflowStep,
982
- 'log.tags': logTags,
983
- 'agent.debug': isDebugSession,
984
- 'agent.isTest': isTestDomain,
985
- };
1239
+ span.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });
1240
+ span.addEvent('cmp.call.error', {
1241
+ 'event.id': eventId,
1242
+ 'cmp.id': componentId,
1243
+ 'cmp.type': componentType,
1244
+ 'cmp.name': componentName,
1245
+ 'error.type': 'result_error',
1246
+ 'error.message': errorMessage,
1247
+ });
986
1248
 
987
- // Only include output if formatOutputForLog returns a value
988
- // if (outputForLog !== undefined) {
989
- // logAttributes['cmp.output'] = outputForLog;
990
- // }
1249
+ // Add output attributes to span
1250
+ span.setAttributes({
1251
+ 'output.size': resultStr.length,
1252
+ 'output.has_error': true,
1253
+ });
991
1254
 
992
- const spanContext = trace.setSpan(context.active(), span);
993
- context.with(spanContext, () => {
994
- logger.emit({
995
- severityNumber: SeverityNumber.INFO,
996
- severityText: 'INFO',
997
- body: `Component ${componentType} (${componentId}) completed successfully`,
998
- attributes: logAttributes,
1255
+ // Emit ERROR log for result error
1256
+ const cmpResultErrorSpanCtx = span.spanContext();
1257
+ const spanContext = trace.setSpan(context.active(), span);
1258
+ context.with(spanContext, () => {
1259
+ logger.emit({
1260
+ severityNumber: SeverityNumber.ERROR,
1261
+ severityText: 'ERROR',
1262
+ body: `Component ${componentType} (${componentId}) failed: ${errorMessage}`,
1263
+ attributes: {
1264
+ // Explicit trace correlation (some backends need these)
1265
+ trace_id: cmpResultErrorSpanCtx.traceId,
1266
+ span_id: cmpResultErrorSpanCtx.spanId,
1267
+ trace_flags: cmpResultErrorSpanCtx.traceFlags,
1268
+
1269
+ 'agent.id': agentId,
1270
+ 'agent.name': agentName,
1271
+ 'process.id': processId,
1272
+ 'event.id': eventId,
1273
+ 'cmp.id': componentId,
1274
+ 'cmp.name': componentName,
1275
+ 'cmp.type': componentType,
1276
+ 'error.type': 'result_error',
1277
+ 'error.message': errorMessage,
1278
+ 'cmp.output': oTelInstance.redactObject(result),
1279
+ 'team.id': teamId,
1280
+ 'org.slot': orgSlot,
1281
+ 'org.tier': orgTier,
1282
+ 'source.id': sourceId,
1283
+ 'source.name': sourceName,
1284
+ 'session.id': sessionId,
1285
+ 'workflow.id': workflowId,
1286
+ 'workflow.step': workflowStep,
1287
+ 'log.tags': logTags,
1288
+ 'agent.debug': isDebugSession,
1289
+ 'agent.isTest': isTestDomain,
1290
+ },
1291
+ });
999
1292
  });
1000
- });
1293
+ } else {
1294
+ // True success case
1295
+ span.setStatus({ code: SpanStatusCode.OK });
1296
+
1297
+ // Add success event with output summary
1298
+ span.addEvent('cmp.call.result', {
1299
+ 'output.size': resultStr.length,
1300
+ 'output.preview': oTelInstance.redactString(resultStr.substring(0, 200)),
1301
+ });
1302
+
1303
+ // Add output attributes to span
1304
+ span.setAttributes({
1305
+ 'output.size': resultStr.length,
1306
+ 'output.has_error': false,
1307
+ });
1308
+
1309
+ // Emit success log with output (formatted safely)
1310
+ const cmpSuccessSpanCtx = span.spanContext();
1311
+ const logAttributes: Record<string, any> = {
1312
+ // Explicit trace correlation (some backends need these)
1313
+ trace_id: cmpSuccessSpanCtx.traceId,
1314
+ span_id: cmpSuccessSpanCtx.spanId,
1315
+ trace_flags: cmpSuccessSpanCtx.traceFlags,
1316
+
1317
+ 'agent.id': agentId,
1318
+ 'agent.name': agentName,
1319
+ 'cmp.id': componentId,
1320
+ 'cmp.type': componentType,
1321
+ 'cmp.name': componentName,
1322
+ 'process.id': processId,
1323
+ 'event.id': eventId,
1324
+ 'cmp.output': oTelInstance.redactObject(result),
1325
+ 'team.id': teamId,
1326
+ 'org.slot': orgSlot,
1327
+ 'org.tier': orgTier,
1328
+ 'source.id': sourceId,
1329
+ 'source.name': sourceName,
1330
+ 'session.id': sessionId,
1331
+ 'workflow.id': workflowId,
1332
+ 'workflow.step': workflowStep,
1333
+ 'log.tags': logTags,
1334
+ 'agent.debug': isDebugSession,
1335
+ 'agent.isTest': isTestDomain,
1336
+ };
1337
+
1338
+ const spanContext = trace.setSpan(context.active(), span);
1339
+ context.with(spanContext, () => {
1340
+ logger.emit({
1341
+ severityNumber: SeverityNumber.INFO,
1342
+ severityText: 'INFO',
1343
+ body: `Component ${componentType} (${componentId}) completed successfully`,
1344
+ attributes: logAttributes,
1345
+ });
1346
+ });
1347
+ }
1001
1348
  }
1002
1349
 
1003
1350
  span.end();
1004
1351
  },
1005
- THook.NonBlocking
1352
+ THook.NonBlocking,
1006
1353
  );
1007
1354
  return Promise.resolve();
1008
1355
  }