@oas-tools/oas-telemetry 0.7.0-alpha.5 → 0.7.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 (106) hide show
  1. package/.env.example +2 -0
  2. package/README.md +35 -17
  3. package/dist/cjs/config/bootConfig.cjs +3 -1
  4. package/dist/cjs/docs/openapi.yaml +1399 -0
  5. package/dist/cjs/routesManager.cjs +1 -1
  6. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.cjs +43 -13
  7. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.cjs +10 -2
  8. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.cjs +21 -16
  9. package/dist/cjs/telemetry/initializeTelemetry.cjs +39 -15
  10. package/dist/cjs/telemetry/telemetryConfigurator.cjs +6 -9
  11. package/dist/cjs/telemetry/telemetryRegistry.cjs +11 -8
  12. package/dist/cjs/tlm-ai/agent.cjs +54 -84
  13. package/dist/cjs/tlm-ai/aiController.cjs +69 -47
  14. package/dist/cjs/tlm-ai/aiRoutes.cjs +10 -3
  15. package/dist/cjs/tlm-ai/aiService.cjs +109 -0
  16. package/dist/cjs/tlm-ai/tools.cjs +30 -268
  17. package/dist/cjs/tlm-auth/authController.cjs +9 -9
  18. package/dist/cjs/tlm-auth/authMiddleware.cjs +1 -1
  19. package/dist/cjs/tlm-log/logController.cjs +30 -36
  20. package/dist/cjs/tlm-log/logRoutes.cjs +3 -2
  21. package/dist/cjs/tlm-metric/metricsController.cjs +15 -8
  22. package/dist/cjs/tlm-metric/metricsRoutes.cjs +2 -1
  23. package/dist/cjs/tlm-plugin/pluginController.cjs +11 -1
  24. package/dist/cjs/tlm-plugin/pluginProcess.cjs +4 -2
  25. package/dist/cjs/tlm-plugin/pluginService.cjs +3 -0
  26. package/dist/cjs/tlm-trace/traceController.cjs +16 -9
  27. package/dist/cjs/tlm-trace/traceRoutes.cjs +2 -1
  28. package/dist/cjs/tlm-util/utilController.cjs +23 -2
  29. package/dist/cjs/tlm-util/utilRoutes.cjs +44 -5
  30. package/dist/cjs/utils/logger.cjs +35 -13
  31. package/dist/esm/config/bootConfig.js +2 -0
  32. package/dist/esm/docs/openapi.yaml +1399 -0
  33. package/dist/esm/routesManager.js +1 -1
  34. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.js +32 -11
  35. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.js +10 -2
  36. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.js +20 -13
  37. package/dist/esm/telemetry/initializeTelemetry.js +22 -14
  38. package/dist/esm/telemetry/telemetryConfigurator.js +7 -10
  39. package/dist/esm/telemetry/telemetryRegistry.js +10 -7
  40. package/dist/esm/tlm-ai/agent.js +37 -78
  41. package/dist/esm/tlm-ai/aiController.js +56 -39
  42. package/dist/esm/tlm-ai/aiRoutes.js +11 -4
  43. package/dist/esm/tlm-ai/aiService.js +94 -0
  44. package/dist/esm/tlm-ai/tools.js +29 -255
  45. package/dist/esm/tlm-auth/authController.js +8 -8
  46. package/dist/esm/tlm-auth/authMiddleware.js +1 -1
  47. package/dist/esm/tlm-log/logController.js +26 -28
  48. package/dist/esm/tlm-log/logRoutes.js +4 -3
  49. package/dist/esm/tlm-metric/metricsController.js +10 -6
  50. package/dist/esm/tlm-metric/metricsRoutes.js +3 -2
  51. package/dist/esm/tlm-plugin/pluginController.js +2 -1
  52. package/dist/esm/tlm-plugin/pluginProcess.js +4 -2
  53. package/dist/esm/tlm-plugin/pluginService.js +4 -0
  54. package/dist/esm/tlm-trace/traceController.js +11 -7
  55. package/dist/esm/tlm-trace/traceRoutes.js +3 -2
  56. package/dist/esm/tlm-util/utilController.js +22 -0
  57. package/dist/esm/tlm-util/utilRoutes.js +40 -5
  58. package/dist/esm/utils/logger.js +35 -12
  59. package/dist/types/config/bootConfig.d.ts +1 -0
  60. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.d.ts +7 -1
  61. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.d.ts +1 -0
  62. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.d.ts +1 -0
  63. package/dist/types/telemetry/telemetryRegistry.d.ts +22 -6
  64. package/dist/types/tlm-ai/agent.d.ts +2 -2
  65. package/dist/types/tlm-ai/aiController.d.ts +5 -4
  66. package/dist/types/tlm-ai/aiRoutes.d.ts +1 -1
  67. package/dist/types/tlm-ai/aiService.d.ts +38 -0
  68. package/dist/types/tlm-ai/tools.d.ts +5 -14
  69. package/dist/types/tlm-log/logController.d.ts +2 -2
  70. package/dist/types/tlm-metric/metricsController.d.ts +2 -1
  71. package/dist/types/tlm-plugin/pluginService.d.ts +2 -0
  72. package/dist/types/tlm-trace/traceController.d.ts +2 -1
  73. package/dist/types/tlm-util/utilController.d.ts +1 -0
  74. package/dist/types/utils/logger.d.ts +5 -5
  75. package/dist/ui/assets/ApiDocsPage-C_VVPPHa.js +16 -0
  76. package/dist/ui/assets/CollapsibleCard-B3KR_8mL.js +1 -0
  77. package/dist/ui/assets/DevToolsPage-OyZcDcmw.js +1 -0
  78. package/dist/ui/assets/LandingPage-CppFBA6K.js +6 -0
  79. package/dist/ui/assets/LogsPage-9Fq8GArS.js +26 -0
  80. package/dist/ui/assets/NotFoundPage-B3quk3P1.js +1 -0
  81. package/dist/ui/assets/PluginCreatePage-X_aCH4t4.js +50 -0
  82. package/dist/ui/assets/PluginPage-DMDSihrZ.js +27 -0
  83. package/dist/ui/assets/alert-jQ9HCPIf.js +1133 -0
  84. package/dist/ui/assets/badge-CNq0-mH5.js +1 -0
  85. package/dist/ui/assets/card-DFAwwhN3.js +1 -0
  86. package/dist/ui/assets/chevron-down-CPsvsmqj.js +6 -0
  87. package/dist/ui/assets/chevron-up-Df9jMo1X.js +6 -0
  88. package/dist/ui/assets/circle-alert-DOPQPvU8.js +6 -0
  89. package/dist/ui/assets/index-BkD6DijD.js +15 -0
  90. package/dist/ui/assets/index-CERGVYZK.js +292 -0
  91. package/dist/ui/assets/index-CSIPf9qw.css +1 -0
  92. package/dist/ui/assets/input-Dzvg_ZEZ.js +1 -0
  93. package/dist/ui/assets/label-DuVnkZ4q.js +1 -0
  94. package/dist/ui/assets/loader-circle-CrvlRy5o.js +6 -0
  95. package/dist/ui/assets/loginPage-qa4V-B70.js +6 -0
  96. package/dist/ui/assets/select-DhS8YUtJ.js +1 -0
  97. package/dist/ui/assets/separator-isK4chBP.js +6 -0
  98. package/dist/ui/assets/severityOptions-O38dSOfk.js +11 -0
  99. package/dist/ui/assets/switch-Z3mImG9n.js +1 -0
  100. package/dist/ui/assets/tabs-_77MUUQe.js +16 -0
  101. package/dist/ui/assets/upload-C1LT4Gkb.js +16 -0
  102. package/dist/ui/assets/utilService-DNyqzwj0.js +1 -0
  103. package/dist/ui/index.html +2 -2
  104. package/package.json +17 -6
  105. package/dist/ui/assets/index-Bgd7fFFH.js +0 -1743
  106. package/dist/ui/assets/index-Cz3N1n1Q.css +0 -1
@@ -46,6 +46,7 @@ export const configureRoutes = (router, oasTlmConfig) => {
46
46
  });
47
47
  // WARNING: This path must be the same as the one used in the UI package App.tsx "oas-telemetry-ui"
48
48
  telemetryRouter.use("/oas-telemetry-ui", getUIRoutes());
49
+ telemetryRouter.use("/utils", getUtilsRoutes(oasTlmConfig));
49
50
  // Auth routes must be registered. If authentication is not enabled, all requests will be allowed.
50
51
  // Frontend will use these endpoints;
51
52
  telemetryRouter.use(cookieParser());
@@ -58,7 +59,6 @@ export const configureRoutes = (router, oasTlmConfig) => {
58
59
  if (oasTlmConfig.ai.openAIKey) {
59
60
  telemetryRouter.use("/ai", getAIRoutes(oasTlmConfig));
60
61
  }
61
- telemetryRouter.use("/utils", getUtilsRoutes(oasTlmConfig));
62
62
  telemetryRouter.use("/plugins", getPluginRoutes());
63
63
  // Mount the telemetryRouter under telemetryBaseUrl
64
64
  router.use(telemetryBaseUrl, telemetryRouter);
@@ -5,6 +5,7 @@ import MiniSearch from 'minisearch';
5
5
  import { applyNesting, removeCircularRefs } from '../utils/circular.js';
6
6
  import { Enabler } from '../wrappers.js';
7
7
  import logger from '../../../utils/logger.js';
8
+ import { pluginService } from '../../../tlm-plugin/pluginService.js';
8
9
  export class InMemoryDbLogExporter extends Enabler {
9
10
  constructor(retentionTimeInSeconds = 3600) {
10
11
  super();
@@ -30,10 +31,6 @@ export class InMemoryDbLogExporter extends Enabler {
30
31
  * @param resultCallback
31
32
  */
32
33
  export(logs, resultCallback) {
33
- if (!this.isEnabled()) {
34
- resultCallback({ code: ExportResultCode.SUCCESS });
35
- return;
36
- }
37
34
  const logsToInsert = logs.map(logRecord => {
38
35
  // Remove circular references first, then apply nesting, then export info
39
36
  const formattedLog = this._formatLogRecord(logRecord);
@@ -41,7 +38,14 @@ export class InMemoryDbLogExporter extends Enabler {
41
38
  const nestedLog = applyNesting(cleanedLog);
42
39
  return nestedLog;
43
40
  });
44
- this._insertLogs(logsToInsert, resultCallback);
41
+ logsToInsert.forEach(log => {
42
+ pluginService.broadcastLog(log);
43
+ });
44
+ // ENABLED only affect storage not plugin broadcasting
45
+ if (this.isEnabled()) {
46
+ this._insertLogs(logsToInsert, resultCallback);
47
+ }
48
+ resultCallback({ code: ExportResultCode.SUCCESS });
45
49
  }
46
50
  reset() {
47
51
  this._db = new Datastore();
@@ -58,15 +62,28 @@ export class InMemoryDbLogExporter extends Enabler {
58
62
  this._db = null;
59
63
  this._miniSearch = null;
60
64
  }
61
- find(query, messageSearch, callback) {
65
+ async find(findConfig) {
66
+ const { query, messageSearch, limit, sortOrder } = findConfig;
67
+ const finalQuery = { ...query };
68
+ const effectiveSortOrder = sortOrder || { timestamp: -1 };
62
69
  if (messageSearch) {
63
- const searchResults = this._miniSearch.search(messageSearch);
70
+ const searchResults = this._miniSearch.search(messageSearch, { prefix: true, fuzzy: 0.2 });
64
71
  const ids = searchResults.map((result) => result._id);
65
72
  logger.debug(`MiniSearch found ${ids.length} results for search term "${messageSearch}"`, { depth: 3 });
66
- // Add MiniSearch results to the query
67
- query._id = { $in: ids };
73
+ finalQuery._id = { $in: ids };
68
74
  }
69
- this._db.find(query, callback);
75
+ const docs = await new Promise((resolve, reject) => {
76
+ this._db.find(finalQuery)
77
+ .sort(effectiveSortOrder)
78
+ .limit(limit)
79
+ .exec((err, docs) => {
80
+ if (err)
81
+ reject(err);
82
+ else
83
+ resolve(docs);
84
+ });
85
+ });
86
+ return docs;
70
87
  }
71
88
  insert(data, callback) {
72
89
  this._insertLogs(data, (result) => {
@@ -100,7 +117,8 @@ export class InMemoryDbLogExporter extends Enabler {
100
117
  attributes: logRecord.resource.attributes,
101
118
  },
102
119
  instrumentationScope: logRecord.instrumentationScope,
103
- timestamp: hrTimeToMicroseconds(logRecord.hrTime) || Date.now(),
120
+ timestamp: hrTimeToMicroseconds(logRecord.hrTime) ?? Date.now(),
121
+ observedTimestamp: hrTimeToMicroseconds(logRecord.hrTimeObserved) ?? Date.now(),
104
122
  traceId: logRecord.spanContext?.traceId,
105
123
  spanId: logRecord.spanContext?.spanId,
106
124
  traceFlags: logRecord.spanContext?.traceFlags,
@@ -114,6 +132,9 @@ export class InMemoryDbLogExporter extends Enabler {
114
132
  this._retentionTimeInSeconds = retentionTimeInSeconds;
115
133
  logger.info(`InMemoryDbLogExporter retention time set to ${this._retentionTimeInSeconds} seconds`);
116
134
  }
135
+ get retentionTimeInSeconds() {
136
+ return this._retentionTimeInSeconds;
137
+ }
117
138
  _insertLogs(logsToInsert, resultCallback) {
118
139
  this._db.insert(logsToInsert, (err, newDocs) => {
119
140
  if (err) {
@@ -3,6 +3,7 @@ import dataStore from '@seald-io/nedb';
3
3
  import { applyNesting } from '../utils/circular.js';
4
4
  import { Enabler } from '../wrappers.js';
5
5
  import logger from '../../../utils/logger.js';
6
+ import { pluginService } from '../../../tlm-plugin/pluginService.js';
6
7
  export class InMemoryDbMetricExporter extends Enabler {
7
8
  constructor(retentionTimeInSeconds = 3600) {
8
9
  super();
@@ -13,9 +14,13 @@ export class InMemoryDbMetricExporter extends Enabler {
13
14
  }
14
15
  export(metrics, resultCallback) {
15
16
  try {
17
+ const scopeMetrics = metrics?.scopeMetrics;
18
+ const cleanMetrics = applyNesting(scopeMetrics);
19
+ cleanMetrics.forEach((metric) => {
20
+ pluginService.broadcastMetric(metric);
21
+ });
22
+ // Insert only if exporter is enabled
16
23
  if (this.isEnabled()) {
17
- const scopeMetrics = metrics?.scopeMetrics;
18
- const cleanMetrics = applyNesting(scopeMetrics);
19
24
  this._metrics.insert(cleanMetrics, (err, _newDoc) => {
20
25
  if (err) {
21
26
  logger.error('Insertion Error:', err);
@@ -62,6 +67,9 @@ export class InMemoryDbMetricExporter extends Enabler {
62
67
  this._retentionTimeInSeconds = retentionTimeInSeconds;
63
68
  logger.info(`InMemoryDbMetricExporter retention time set to ${this._retentionTimeInSeconds} seconds`);
64
69
  }
70
+ get retentionTimeInSeconds() {
71
+ return this._retentionTimeInSeconds;
72
+ }
65
73
  _startCleanupJob() {
66
74
  const interval = 1000;
67
75
  setInterval(() => {
@@ -3,6 +3,7 @@ import dataStore from '@seald-io/nedb';
3
3
  import logger from '../../../utils/logger.js';
4
4
  import { applyNesting, removeCircularRefs } from '../utils/circular.js';
5
5
  import { Enabler } from '../wrappers.js';
6
+ import { pluginService } from '../../../tlm-plugin/pluginService.js';
6
7
  export class InMemoryDbSpanExporter extends Enabler {
7
8
  constructor(retentionTimeInSeconds = 3600) {
8
9
  super();
@@ -20,32 +21,38 @@ export class InMemoryDbSpanExporter extends Enabler {
20
21
  this._retentionTimeInSeconds = retentionTimeInSeconds;
21
22
  logger.info(`InMemoryDbSpanExporter retention time set to ${this._retentionTimeInSeconds} seconds`);
22
23
  }
24
+ get retentionTimeInSeconds() {
25
+ return this._retentionTimeInSeconds;
26
+ }
23
27
  export(readableSpans, resultCallback) {
24
28
  logger.debug('InMemoryDbSpanExporter.export called with spans: ', readableSpans.length);
25
29
  try {
26
- if (!this.isEnabled()) {
27
- logger.debug('InMemoryDbSpanExporter is not enabled. Skipping export.');
28
- return resultCallback({ code: ExportResultCode.SUCCESS });
29
- }
30
30
  // Prepare spans to be inserted into the in-memory database (remove circular references and convert to nested objects)
31
31
  const cleanSpans = readableSpans
32
32
  .map(nestedSpan => removeCircularRefs(nestedSpan)) // to avoid JSON parsing error
33
33
  .map(span => applyNesting(span)) // to avoid dot notation in keys (neDB does not support dot notation in keys)
34
34
  .filter(span => {
35
- const target = span?.attributes?.http?.target; // Exclude spans where target includes 'telemetry' but NOT 'telemetry/utils'
35
+ const target = span?.attributes?.http?.target;
36
+ // Exclude spans where target includes 'telemetry' but NOT 'telemetry/utils/generate-log' or 'telemetry/utils/generate-wait'
36
37
  if (target && target.includes(this._baseUrl)) {
37
- return target.includes(this._baseUrl + '/utils');
38
+ return (target.includes("generate"));
38
39
  }
39
40
  return true;
40
41
  });
41
- // Insert spans into the in-memory database
42
- this._spans.insert(cleanSpans, (err, _newDoc) => {
43
- if (err) {
44
- logger.error(err);
45
- return;
46
- }
42
+ cleanSpans.forEach(span => {
43
+ pluginService.broadcastTrace(span);
47
44
  });
48
- setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
45
+ //
46
+ if (this.isEnabled()) {
47
+ // Insert spans into the in-memory database
48
+ this._spans.insert(cleanSpans, (err, _newDoc) => {
49
+ if (err) {
50
+ logger.error(err);
51
+ return;
52
+ }
53
+ });
54
+ }
55
+ return resultCallback({ code: ExportResultCode.SUCCESS });
49
56
  }
50
57
  catch (error) {
51
58
  logger.error('Error exporting spans\n' + error.message + '\n' + error.stack);
@@ -1,11 +1,12 @@
1
1
  import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
2
2
  import logger from '../utils/logger.js';
3
3
  import { registerInstrumentations } from '@opentelemetry/instrumentation';
4
- import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, oasTelemetryResource } from './telemetryRegistry.js';
4
+ import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, oasTelemetryResource, originalConsoleMethods } from './telemetryRegistry.js';
5
5
  import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
6
6
  import { SeverityNumber } from '@opentelemetry/api-logs';
7
7
  import { LoggerProvider } from '@opentelemetry/sdk-logs';
8
8
  import { bootEnvVariables } from '../config/bootConfig.js';
9
+ import util from 'util';
9
10
  // THIS INSTRUMENTATIONS NEED TO BE LOADED BEFORE ANYTHING ELSE
10
11
  // They use monkey-patching to instrument the HTTP server and client.
11
12
  // THIS FILE MUST BE CALLED BEFORE ANYTHING ELSE
@@ -41,22 +42,15 @@ function initializeLogs() {
41
42
  });
42
43
  // Get a logger instance
43
44
  const loggerInstance = loggerProvider.getLogger('oas-telemetry'); // Use loggerProvider to get the logger
44
- // Override console methods to emit logs via OpenTelemetry, like an instrumentation
45
- const originalConsoleMethods = {
46
- log: console.log,
47
- warn: console.warn,
48
- error: console.error,
49
- info: console.info,
50
- debug: console.debug,
51
- };
52
45
  Object.keys(originalConsoleMethods).forEach((method) => {
53
46
  // @ts-expect-error yes
54
47
  console[method] = (...args) => {
48
+ const severity = getSeverityForMethod(method);
55
49
  loggerInstance.emit({
56
- severityNumber: SeverityNumber[method.toUpperCase()] || SeverityNumber.INFO,
57
- severityText: method.toUpperCase(),
58
- body: args.join(' '),
59
- attributes: { 'source.source': `console.${method}` },
50
+ severityNumber: severity.number,
51
+ severityText: severity.text,
52
+ body: util.format(...args),
53
+ attributes: { 'source': `console.${method}`, "library": "oas-telemetry" },
60
54
  });
61
55
  // @ts-expect-error yes
62
56
  originalConsoleMethods[method](...args);
@@ -65,7 +59,21 @@ function initializeLogs() {
65
59
  }
66
60
  function initializeMetrics() {
67
61
  logger.info('📈 Initializing MeterProvider');
68
- // WARN: This is a custom provider that allows adding readers dynamically at runtime.
69
62
  // WARN: Default PeriodicExportingMetricReader is added post initialization (see telemetryConfigurator.ts)
70
63
  // The in memory exporter is added by default to that reader. More readers are allowed to be added dynamically
71
64
  }
65
+ function getSeverityForMethod(method) {
66
+ switch (method) {
67
+ case "log":
68
+ case "info":
69
+ return { number: SeverityNumber.INFO, text: "INFO" };
70
+ case "debug":
71
+ return { number: SeverityNumber.DEBUG, text: "DEBUG" };
72
+ case "warn":
73
+ return { number: SeverityNumber.WARN, text: "WARN" };
74
+ case "error":
75
+ return { number: SeverityNumber.ERROR, text: "ERROR" };
76
+ default:
77
+ return { number: SeverityNumber.INFO, text: "INFO" };
78
+ }
79
+ }
@@ -1,23 +1,27 @@
1
1
  import { BatchSpanProcessor, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
2
- import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, inMemoryDbLogExporter, inMemoryDbMetricExporter, inMemoryDbSpanExporter, multiLogExporter, multiSpanExporter, oasTelemetryResource, pluginLogExporter, pluginMetricExporter, pluginSpanExporter } from './telemetryRegistry.js';
2
+ import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, inMemoryDbLogExporter, inMemoryDbMetricExporter, inMemoryDbSpanExporter, multiLogExporter, multiSpanExporter, oasTelemetryResource } from './telemetryRegistry.js';
3
3
  import logger from '../utils/logger.js';
4
4
  import { BatchLogRecordProcessor, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs';
5
5
  import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
6
6
  import { HostMetrics } from '@opentelemetry/host-metrics';
7
7
  import { bootEnvVariables } from '../config/bootConfig.js';
8
+ import { pluginService } from '../tlm-plugin/pluginService.js';
8
9
  export const configureTelemetry = (oasTlmConfig) => {
10
+ configurePlugins(oasTlmConfig);
9
11
  configureTraces(oasTlmConfig);
10
12
  configureMetrics(oasTlmConfig);
11
13
  configureLogs(oasTlmConfig);
12
14
  logger.info("✅ Telemetry configured successfully. All exporters are ready");
13
15
  };
16
+ function configurePlugins(oasTlmConfig) {
17
+ pluginService.enabled = oasTlmConfig.plugins.enabled;
18
+ }
14
19
  function configureTraces(oasTlmConfig) {
15
20
  // TRACES CONFIGURATION
16
21
  // [OT]Provider -> [OT]SpanProcessor(multiSpan) -> n Processors(eg mainProcessor, extra) -> 1 SpanExporter
17
22
  inMemoryDbSpanExporter.baseUrl = oasTlmConfig.general.baseUrl; // TODO this will be done with filters
18
23
  inMemoryDbSpanExporter.retentionTimeInSeconds = oasTlmConfig.traces.memoryExporter.retentionTimeSeconds;
19
24
  inMemoryDbSpanExporter.setEnabledValue(oasTlmConfig.traces.memoryExporter.enabled);
20
- pluginSpanExporter.setEnabledValue(oasTlmConfig.plugins.enabled);
21
25
  const mainExporter = multiSpanExporter;
22
26
  let mainProcessor = new BatchSpanProcessor(mainExporter);
23
27
  if (bootEnvVariables.OASTLM_BOOT_ENV !== 'production') {
@@ -25,7 +29,6 @@ function configureTraces(oasTlmConfig) {
25
29
  mainProcessor = new SimpleSpanProcessor(mainExporter);
26
30
  }
27
31
  mainExporter.addExporters(inMemoryDbSpanExporter); // Main exporter have at least the in-memory exporter used by the traces controller
28
- mainExporter.addExporters(pluginSpanExporter);
29
32
  mainExporter.addExporters(oasTlmConfig.traces.extraExporters);
30
33
  dynamicMultiSpanProcessor.addProcessors(mainProcessor);
31
34
  dynamicMultiSpanProcessor.addProcessors(oasTlmConfig.traces.extraProcessors);
@@ -43,7 +46,6 @@ function configureLogs(oasTlmConfig) {
43
46
  }
44
47
  mainExporter.addExporters(inMemoryDbLogExporter); // Main exporter have at least the in-memory exporter used by the logs controller
45
48
  mainExporter.addExporters(oasTlmConfig.logs.extraExporters);
46
- mainExporter.addExporters(pluginLogExporter); // Allow logs to be sent to plugins too
47
49
  dynamicMultiLogProcessor.addProcessors(mainProcessor);
48
50
  dynamicMultiLogProcessor.addProcessors(oasTlmConfig.logs.extraProcessors);
49
51
  }
@@ -57,14 +59,9 @@ function configureMetrics(oasTlmConfig) {
57
59
  exportIntervalMillis: oasTlmConfig.metrics.mainMetricReaderOptions.exportIntervalMillis,
58
60
  metricProducers: oasTlmConfig.metrics.mainMetricReaderOptions.metricProducers
59
61
  });
60
- const pluginReader = new PeriodicExportingMetricReader({
61
- exporter: pluginMetricExporter,
62
- exportIntervalMillis: oasTlmConfig.metrics.mainMetricReaderOptions.exportIntervalMillis,
63
- metricProducers: oasTlmConfig.metrics.mainMetricReaderOptions.metricProducers
64
- });
65
62
  const meterProvider = new MeterProvider({
66
63
  resource: oasTelemetryResource,
67
- readers: [mainReader, pluginReader, ...oasTlmConfig.metrics.extraReaders],
64
+ readers: [mainReader, ...oasTlmConfig.metrics.extraReaders],
68
65
  views: oasTlmConfig.metrics.extraViews || []
69
66
  });
70
67
  // TODO maybe hostMetrics are too much, consider using only a subset of them.
@@ -6,12 +6,10 @@ import { DynamicMultiSpanProcessor } from "./custom-implementations/processors/d
6
6
  import { DynamicMultiLogRecordProcessor } from "./custom-implementations/processors/dynamicMultiLogProcessor.js";
7
7
  import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
8
8
  import { resourceFromAttributes } from "@opentelemetry/resources";
9
- import { PluginSpanExporter } from "./custom-implementations/exporters/PluginSpanExporter.js";
10
- import { PluginLogExporter } from "./custom-implementations/exporters/PluginLogExporter.js";
11
- import { PluginMetricExporter } from "./custom-implementations/exporters/PluginMetricExporter.js";
9
+ import { bootEnvVariables } from "../config/bootConfig.js";
12
10
  // GLOBAL REGISTRY of telemetry components, used by SDKs and controllers.
13
11
  export const oasTelemetryResource = resourceFromAttributes({
14
- [ATTR_SERVICE_NAME]: 'oas-telemetry-service'
12
+ [ATTR_SERVICE_NAME]: bootEnvVariables.OASTLM_BOOT_SERVICE_NAME
15
13
  });
16
14
  // TRACES -------------------------------------------------------------------------------------
17
15
  // This is the main exporter for oas-telemetry spans. (Used by the traces controller)
@@ -20,15 +18,20 @@ export const inMemoryDbSpanExporter = new InMemoryDbSpanExporter();
20
18
  export const multiSpanExporter = new EnablerMultiSpanExporter();
21
19
  // This allows the addition of more processors at runtime
22
20
  export const dynamicMultiSpanProcessor = new DynamicMultiSpanProcessor();
23
- export const pluginSpanExporter = new PluginSpanExporter(); // This exporter sends spans to the plugin module.
24
21
  // LOGS ----------------------------------------------------------------------------------------
25
22
  export const inMemoryDbLogExporter = new InMemoryDbLogExporter();
26
23
  export const multiLogExporter = new EnablerMultiLogExporter();
27
24
  export const dynamicMultiLogProcessor = new DynamicMultiLogRecordProcessor();
28
- export const pluginLogExporter = new PluginLogExporter(); // This exporter sends logs to the plugin module.
25
+ // Override console methods to emit logs via OpenTelemetry, like an instrumentation
26
+ export const originalConsoleMethods = {
27
+ log: console.log,
28
+ warn: console.warn,
29
+ error: console.error,
30
+ info: console.info,
31
+ debug: console.debug,
32
+ };
29
33
  // METRICS -------------------------------------------------------------------------------------
30
34
  // Metrics follow a different pattern in OpenTelemetry
31
35
  export const inMemoryDbMetricExporter = new InMemoryDbMetricExporter();
32
- export const pluginMetricExporter = new PluginMetricExporter(); // This exporter sends metrics to the plugin module.
33
36
  // Readers and their exporters cannot be grouped together in a MultiReader or similar construct
34
37
  // due to differences in aggregation temporality and aggregation selection.
@@ -1,86 +1,45 @@
1
- import OpenAI from 'openai';
2
1
  import { tools, availableTools } from './tools.js';
3
2
  import logger from '../utils/logger.js';
4
- export function getAgent(oasTlmConfig) {
5
- let openai;
6
- try {
7
- if (!oasTlmConfig.ai.openAIKey) {
8
- openai = null;
9
- }
10
- else {
11
- openai = new OpenAI({
12
- apiKey: oasTlmConfig.ai.openAIKey ?? undefined,
13
- dangerouslyAllowBrowser: true,
14
- });
15
- }
16
- }
17
- catch {
18
- openai = null;
19
- }
20
- const messages = [
21
- {
22
- role: "assistant",
23
- content: "You are a helpful telemetry assistant. Only use the functions you have been provided with. If the question is not related to the functions, respond with 'I cannot help with that.'. If you need to call to other agents, do so using the tools provided."
24
- },
25
- ];
26
- // Add extra context prompts if provided
27
- if (oasTlmConfig.ai.extraContextPrompts) {
28
- for (const prompt of oasTlmConfig.ai.extraContextPrompts) {
29
- messages.push({
30
- role: "system",
31
- content: prompt,
32
- });
33
- }
34
- }
35
- async function agent(userInput) {
36
- if (!openai) {
37
- logger.error("OpenAI client is not initialized. Please check your OpenAI API key.");
38
- return { content: "OpenAI client is not initialized. Please check your OpenAI API key." };
39
- }
40
- messages.push({
41
- role: "user",
42
- content: userInput,
3
+ export async function agent(openai, messages, model = "gpt-3.5-turbo", extraPrompts = []) {
4
+ for (let i = 0; i < 5; i++) {
5
+ const modelResponse = await openai.chat.completions.create({
6
+ model,
7
+ messages,
8
+ tools,
43
9
  });
44
- for (let i = 0; i < 5; i++) {
45
- const response = await (openai?.chat?.completions?.create?.({
46
- model: oasTlmConfig.ai.openAIModel,
47
- messages: messages,
48
- tools: tools,
49
- }));
50
- const { finish_reason, message } = response.choices[0];
51
- if (finish_reason === "tool_calls" && message.tool_calls) {
52
- logger.debug("Tool calls detected:", message.tool_calls);
53
- const results = [];
54
- for (const toolCall of message.tool_calls) {
55
- const functionName = toolCall.function.name;
56
- const functionToCall = availableTools[functionName];
57
- const functionArgs = JSON.parse(toolCall.function.arguments);
58
- const functionArgsArr = Object.values(functionArgs);
59
- // @ts-expect-error yes
60
- // eslint-disable-next-line prefer-spread
61
- const functionResponse = await functionToCall.apply(null, functionArgsArr);
62
- results.push({
63
- name: functionName,
64
- response: functionResponse,
65
- });
66
- }
67
- const resultMessage = results.map(({ name, response }) => `Result from "${name}":\n${JSON.stringify(response, null, 2)}`).join("\n\n");
68
- messages.push({
69
- role: "function",
70
- name: "multiple_tool_calls",
71
- content: resultMessage,
10
+ const { finish_reason, message } = modelResponse.choices[0];
11
+ if (finish_reason === "tool_calls" && message.tool_calls) {
12
+ logger.debug("Tool calls detected:", message.tool_calls);
13
+ const results = [];
14
+ for (const toolCall of message.tool_calls) {
15
+ const functionName = toolCall.function.name;
16
+ const functionToCall = availableTools[functionName];
17
+ const functionArgs = JSON.parse(toolCall.function.arguments);
18
+ const functionArgsArr = Object.values(functionArgs);
19
+ // @ts-expect-error yes
20
+ // eslint-disable-next-line prefer-spread
21
+ const functionResponse = await functionToCall.apply(null, functionArgsArr);
22
+ results.push({
23
+ name: functionName,
24
+ response: functionResponse,
72
25
  });
73
26
  }
74
- else if (finish_reason === "stop") {
75
- messages.push(message);
76
- return message;
77
- }
27
+ const resultMessage = results.map(({ name, response }, idx) => {
28
+ const toolCall = message.tool_calls?.[idx];
29
+ const params = toolCall ? JSON.parse(toolCall.function.arguments) : {};
30
+ return `Tool "${name}" called with parameters:\n${JSON.stringify(params, null, 2)}\nResult:\n${JSON.stringify(response, null, 2)}`;
31
+ }).join("\n\n");
32
+ messages.push({
33
+ role: "function",
34
+ name: "multiple_tool_calls",
35
+ content: resultMessage,
36
+ timestamp: new Date().toISOString()
37
+ });
38
+ }
39
+ else if (finish_reason === "stop") {
40
+ messages.push({ ...message, timestamp: new Date().toISOString() });
41
+ return;
78
42
  }
79
- return { content: "Se alcanzó el número máximo de iteraciones sin una respuesta adecuada. Intenta con una consulta más específica." };
80
43
  }
81
- return async function getAgentResponse(question) {
82
- const response = await agent(question);
83
- logger.debug("Response from agent:", response);
84
- return response.content;
85
- };
44
+ messages.push({ role: "assistant", content: "Maximum iterations reached without a suitable response. Try a more specific query.", timestamp: new Date().toISOString() });
86
45
  }
@@ -1,46 +1,63 @@
1
- import { getAgent } from './agent.js';
2
- import { setKnownMicroservices, getKnownMicroservices } from './knownMicroservices.js';
3
- import logger from '../utils/logger.js';
4
- export const answerQuestion = (oasTlmConfig) => async (req, res) => {
1
+ import { getAiService } from './aiService.js';
2
+ export async function createConversation(req, res) {
5
3
  try {
6
- const { question } = req.body;
7
- if (!question)
8
- res.status(400).json({ error: 'Missing question' });
9
- const getAgentResponse = getAgent(oasTlmConfig);
10
- const answer = await getAgentResponse(question);
11
- res.json({ answer });
12
- }
13
- catch (error) {
14
- logger.error(error);
15
- res.status(500).json({ error: 'Internal error' });
16
- }
17
- };
18
- export const setKnownMicroservicesHandler = () => (req, res) => {
4
+ const conversation = getAiService().createConversation();
5
+ res.status(201).json(conversation);
6
+ }
7
+ catch (err) {
8
+ res.status(500).json({ error: err.message });
9
+ }
10
+ }
11
+ export async function listConversations(req, res) {
12
+ try {
13
+ // Only return id and name for each conversation
14
+ const conversations = getAiService().listConversationsMinimal();
15
+ res.json(conversations);
16
+ }
17
+ catch (err) {
18
+ res.status(500).json({ error: err.message });
19
+ }
20
+ }
21
+ export async function getConversationHistory(req, res) {
22
+ try {
23
+ const { conversationId } = req.params;
24
+ const conversation = getAiService().getConversation(conversationId);
25
+ if (!conversation) {
26
+ res.status(404).json({ error: 'Not found' });
27
+ return;
28
+ }
29
+ res.json(conversation);
30
+ }
31
+ catch (err) {
32
+ res.status(500).json({ error: err.message });
33
+ }
34
+ }
35
+ export async function deleteConversation(req, res) {
19
36
  try {
20
- const { microservices } = req.body;
21
- if (!Array.isArray(microservices)) {
22
- res.status(400).json({ error: 'Invalid microservices format. Expected an array.' });
37
+ const { conversationId } = req.params;
38
+ const deleted = getAiService().deleteConversation(conversationId);
39
+ if (!deleted) {
40
+ res.status(404).json({ error: 'Not found' });
23
41
  return;
24
42
  }
25
- setKnownMicroservices(microservices);
26
- res.json({
27
- message: "Microservices configuration updated successfully.",
28
- knownMicroservices: getKnownMicroservices(),
29
- note: "In the future, OAS-Telemetry will support autodiscovery, making this configuration unnecessary.",
30
- });
31
- }
32
- catch (error) {
33
- logger.error(error);
34
- res.status(500).json({ error: 'Internal error' });
35
- }
36
- };
37
- export const getKnownMicroservicesHandler = () => (req, res) => {
43
+ res.status(204).send();
44
+ }
45
+ catch (err) {
46
+ res.status(500).json({ error: err.message });
47
+ }
48
+ }
49
+ export async function sendMessage(req, res) {
38
50
  try {
39
- const microservices = getKnownMicroservices();
40
- res.json({ knownMicroservices: microservices });
51
+ const { conversationId } = req.params;
52
+ const { content } = req.body;
53
+ if (!content) {
54
+ res.status(400).json({ error: 'Missing content' });
55
+ return;
56
+ }
57
+ const messages = await getAiService().sendMessage(conversationId, content);
58
+ res.json(messages);
41
59
  }
42
- catch (error) {
43
- logger.error(error);
44
- res.status(500).json({ error: 'Internal error' });
60
+ catch (err) {
61
+ res.status(500).json({ error: err.message });
45
62
  }
46
- };
63
+ }
@@ -1,9 +1,16 @@
1
1
  import { Router } from 'express';
2
- import { answerQuestion, setKnownMicroservicesHandler, getKnownMicroservicesHandler } from './aiController.js';
2
+ import { createConversation, deleteConversation, getConversationHistory, listConversations, sendMessage } from './aiController.js';
3
+ import { configureAiService } from './aiService.js';
3
4
  export const getAIRoutes = (oasTlmConfig) => {
4
5
  const router = Router();
5
- router.post('/chat', answerQuestion(oasTlmConfig));
6
- router.post('/microservices', setKnownMicroservicesHandler());
7
- router.get('/microservices', getKnownMicroservicesHandler());
6
+ configureAiService(oasTlmConfig); //oasTlmConfig.ai.openAIKey
7
+ router.get('/chat/health', (req, res) => {
8
+ res.status(200).send('AI service is healthy');
9
+ });
10
+ router.get('/chat', listConversations);
11
+ router.post('/chat', createConversation);
12
+ router.get('/chat/:conversationId', getConversationHistory);
13
+ router.post('/chat/:conversationId/message', sendMessage);
14
+ router.delete('/chat/:conversationId', deleteConversation);
8
15
  return router;
9
16
  };