autotel 2.24.1 → 2.25.1

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 (59) hide show
  1. package/dist/auto.cjs +2 -2
  2. package/dist/auto.js +1 -1
  3. package/dist/{chunk-CSG2D3BZ.cjs → chunk-73SCHI7V.cjs} +7 -7
  4. package/dist/{chunk-CSG2D3BZ.cjs.map → chunk-73SCHI7V.cjs.map} +1 -1
  5. package/dist/{chunk-47KW5CV7.js → chunk-7RV6L24E.js} +4 -3
  6. package/dist/chunk-7RV6L24E.js.map +1 -0
  7. package/dist/{chunk-AXNPD6XW.cjs → chunk-7ZYUFMWU.cjs} +26 -26
  8. package/dist/{chunk-AXNPD6XW.cjs.map → chunk-7ZYUFMWU.cjs.map} +1 -1
  9. package/dist/{chunk-GSZJOYFF.js → chunk-BKIAH2YS.js} +3 -3
  10. package/dist/{chunk-GSZJOYFF.js.map → chunk-BKIAH2YS.js.map} +1 -1
  11. package/dist/{chunk-CLR5RQW4.cjs → chunk-DBUNRUDB.cjs} +5 -5
  12. package/dist/{chunk-CLR5RQW4.cjs.map → chunk-DBUNRUDB.cjs.map} +1 -1
  13. package/dist/{chunk-PDRIJURL.cjs → chunk-GIND746I.cjs} +4 -3
  14. package/dist/chunk-GIND746I.cjs.map +1 -0
  15. package/dist/{chunk-CZR7PJUZ.cjs → chunk-HCFBEOBM.cjs} +13 -13
  16. package/dist/{chunk-CZR7PJUZ.cjs.map → chunk-HCFBEOBM.cjs.map} +1 -1
  17. package/dist/{chunk-2D74YR4Z.js → chunk-JDV3LYOI.js} +3 -3
  18. package/dist/{chunk-2D74YR4Z.js.map → chunk-JDV3LYOI.js.map} +1 -1
  19. package/dist/{chunk-ZJTBAUXG.js → chunk-JZKMXKJX.js} +3 -3
  20. package/dist/{chunk-ZJTBAUXG.js.map → chunk-JZKMXKJX.js.map} +1 -1
  21. package/dist/{chunk-W6U2BUHU.cjs → chunk-LRFG6HRL.cjs} +5 -5
  22. package/dist/{chunk-W6U2BUHU.cjs.map → chunk-LRFG6HRL.cjs.map} +1 -1
  23. package/dist/{chunk-Z3BQYTZE.js → chunk-NYTCPK4J.js} +3 -3
  24. package/dist/{chunk-Z3BQYTZE.js.map → chunk-NYTCPK4J.js.map} +1 -1
  25. package/dist/{chunk-IJMW6CTV.js → chunk-P5YCN2Z3.js} +3 -3
  26. package/dist/{chunk-IJMW6CTV.js.map → chunk-P5YCN2Z3.js.map} +1 -1
  27. package/dist/decorators.cjs +2 -2
  28. package/dist/decorators.js +2 -2
  29. package/dist/event.cjs +5 -5
  30. package/dist/event.js +2 -2
  31. package/dist/functional.cjs +9 -9
  32. package/dist/functional.js +2 -2
  33. package/dist/index.cjs +41 -41
  34. package/dist/index.js +9 -9
  35. package/dist/instrumentation.cjs +8 -8
  36. package/dist/instrumentation.js +1 -1
  37. package/dist/messaging.cjs +6 -6
  38. package/dist/messaging.js +3 -3
  39. package/dist/semantic-helpers.cjs +7 -7
  40. package/dist/semantic-helpers.js +3 -3
  41. package/dist/test-span-collector.cjs +1 -0
  42. package/dist/test-span-collector.cjs.map +1 -1
  43. package/dist/test-span-collector.d.cts +2 -1
  44. package/dist/test-span-collector.d.ts +2 -1
  45. package/dist/test-span-collector.js +1 -1
  46. package/dist/test-span-collector.js.map +1 -1
  47. package/dist/webhook.cjs +3 -3
  48. package/dist/webhook.js +2 -2
  49. package/dist/workflow-distributed.cjs +4 -4
  50. package/dist/workflow-distributed.js +2 -2
  51. package/dist/workflow.cjs +7 -7
  52. package/dist/workflow.js +3 -3
  53. package/package.json +12 -10
  54. package/src/init.integrations.test.ts +14 -8
  55. package/src/init.openllmetry.test.ts +98 -193
  56. package/src/init.ts +18 -1
  57. package/src/test-span-collector.ts +1 -1
  58. package/dist/chunk-47KW5CV7.js.map +0 -1
  59. package/dist/chunk-PDRIJURL.cjs.map +0 -1
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkINJD3G4K_cjs = require('./chunk-INJD3G4K.cjs');
4
- var chunkAXNPD6XW_cjs = require('./chunk-AXNPD6XW.cjs');
4
+ var chunk7ZYUFMWU_cjs = require('./chunk-7ZYUFMWU.cjs');
5
5
  require('./chunk-W4EUTSB2.cjs');
6
6
  require('./chunk-GML3FBOT.cjs');
7
7
  require('./chunk-D5LMF53P.cjs');
8
8
  require('./chunk-XRKAL7WJ.cjs');
9
- require('./chunk-PDRIJURL.cjs');
9
+ require('./chunk-GIND746I.cjs');
10
10
  require('./chunk-6YIDHH2S.cjs');
11
11
  require('./chunk-GVLK7YUU.cjs');
12
12
  require('./chunk-ZNMBW67B.cjs');
@@ -59,7 +59,7 @@ var WorkflowBaggage = chunkINJD3G4K_cjs.createSafeBaggageSchema(workflowBaggageF
59
59
  function traceDistributedWorkflow(config) {
60
60
  const spanName = `workflow.${config.name}`;
61
61
  return (fnFactory) => {
62
- return chunkAXNPD6XW_cjs.trace(
62
+ return chunk7ZYUFMWU_cjs.trace(
63
63
  { name: spanName, spanKind: api.SpanKind.INTERNAL },
64
64
  (baseCtx) => {
65
65
  return async (...args) => {
@@ -158,7 +158,7 @@ function traceDistributedWorkflow(config) {
158
158
  function traceDistributedStep(config) {
159
159
  const spanName = `workflow.step.${config.name}`;
160
160
  return (fnFactory) => {
161
- return chunkAXNPD6XW_cjs.trace(
161
+ return chunk7ZYUFMWU_cjs.trace(
162
162
  { name: spanName, spanKind: api.SpanKind.INTERNAL },
163
163
  (baseCtx) => {
164
164
  return async (...args) => {
@@ -1,10 +1,10 @@
1
1
  import { createSafeBaggageSchema } from './chunk-4IFSYQVX.js';
2
- import { trace } from './chunk-2D74YR4Z.js';
2
+ import { trace } from './chunk-JDV3LYOI.js';
3
3
  import './chunk-SR35DG5A.js';
4
4
  import './chunk-B3ZHLLMP.js';
5
5
  import './chunk-WD4RP6IV.js';
6
6
  import './chunk-USSL3D6L.js';
7
- import './chunk-47KW5CV7.js';
7
+ import './chunk-7RV6L24E.js';
8
8
  import './chunk-YTGF4L2C.js';
9
9
  import './chunk-X4RMFFMR.js';
10
10
  import './chunk-WGWSHJ2N.js';
package/dist/workflow.cjs CHANGED
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var chunkCLR5RQW4_cjs = require('./chunk-CLR5RQW4.cjs');
4
- require('./chunk-AXNPD6XW.cjs');
3
+ var chunkDBUNRUDB_cjs = require('./chunk-DBUNRUDB.cjs');
4
+ require('./chunk-7ZYUFMWU.cjs');
5
5
  require('./chunk-W4EUTSB2.cjs');
6
6
  require('./chunk-GML3FBOT.cjs');
7
7
  require('./chunk-D5LMF53P.cjs');
8
8
  require('./chunk-XRKAL7WJ.cjs');
9
- require('./chunk-PDRIJURL.cjs');
9
+ require('./chunk-GIND746I.cjs');
10
10
  require('./chunk-6YIDHH2S.cjs');
11
11
  require('./chunk-GVLK7YUU.cjs');
12
12
  require('./chunk-ZNMBW67B.cjs');
@@ -25,19 +25,19 @@ require('./chunk-JEQ2X3Z6.cjs');
25
25
 
26
26
  Object.defineProperty(exports, "getCurrentWorkflowContext", {
27
27
  enumerable: true,
28
- get: function () { return chunkCLR5RQW4_cjs.getCurrentWorkflowContext; }
28
+ get: function () { return chunkDBUNRUDB_cjs.getCurrentWorkflowContext; }
29
29
  });
30
30
  Object.defineProperty(exports, "isInWorkflow", {
31
31
  enumerable: true,
32
- get: function () { return chunkCLR5RQW4_cjs.isInWorkflow; }
32
+ get: function () { return chunkDBUNRUDB_cjs.isInWorkflow; }
33
33
  });
34
34
  Object.defineProperty(exports, "traceStep", {
35
35
  enumerable: true,
36
- get: function () { return chunkCLR5RQW4_cjs.traceStep; }
36
+ get: function () { return chunkDBUNRUDB_cjs.traceStep; }
37
37
  });
38
38
  Object.defineProperty(exports, "traceWorkflow", {
39
39
  enumerable: true,
40
- get: function () { return chunkCLR5RQW4_cjs.traceWorkflow; }
40
+ get: function () { return chunkDBUNRUDB_cjs.traceWorkflow; }
41
41
  });
42
42
  //# sourceMappingURL=workflow.cjs.map
43
43
  //# sourceMappingURL=workflow.cjs.map
package/dist/workflow.js CHANGED
@@ -1,10 +1,10 @@
1
- export { getCurrentWorkflowContext, isInWorkflow, traceStep, traceWorkflow } from './chunk-IJMW6CTV.js';
2
- import './chunk-2D74YR4Z.js';
1
+ export { getCurrentWorkflowContext, isInWorkflow, traceStep, traceWorkflow } from './chunk-P5YCN2Z3.js';
2
+ import './chunk-JDV3LYOI.js';
3
3
  import './chunk-SR35DG5A.js';
4
4
  import './chunk-B3ZHLLMP.js';
5
5
  import './chunk-WD4RP6IV.js';
6
6
  import './chunk-USSL3D6L.js';
7
- import './chunk-47KW5CV7.js';
7
+ import './chunk-7RV6L24E.js';
8
8
  import './chunk-YTGF4L2C.js';
9
9
  import './chunk-X4RMFFMR.js';
10
10
  import './chunk-WGWSHJ2N.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel",
3
- "version": "2.24.1",
3
+ "version": "2.25.1",
4
4
  "description": "Write Once, Observe Anywhere",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -139,11 +139,13 @@
139
139
  },
140
140
  "./register": {
141
141
  "types": "./dist/register.d.ts",
142
- "import": "./dist/register.js"
142
+ "import": "./dist/register.js",
143
+ "require": "./dist/register.cjs"
143
144
  },
144
145
  "./auto": {
145
146
  "types": "./dist/auto.d.ts",
146
- "import": "./dist/auto.js"
147
+ "import": "./dist/auto.js",
148
+ "require": "./dist/auto.cjs"
147
149
  },
148
150
  "./hook.mjs": {
149
151
  "import": "./src/hook.mjs"
@@ -257,7 +259,7 @@
257
259
  "@opentelemetry/sdk-node": "^0.213.0",
258
260
  "@opentelemetry/sdk-trace-base": "^2.6.0",
259
261
  "@opentelemetry/semantic-conventions": "^1.40.0",
260
- "@tanstack/intent": "^0.0.19",
262
+ "@tanstack/intent": "^0.0.23",
261
263
  "import-in-the-middle": "^3.0.0"
262
264
  },
263
265
  "peerDependencies": {
@@ -274,7 +276,7 @@
274
276
  "@traceloop/node-server-sdk": "^0.22.8",
275
277
  "pino": "^10.3.1",
276
278
  "pino-pretty": "^13.1.3",
277
- "yaml": "^2.8.2"
279
+ "yaml": "^2.8.3"
278
280
  },
279
281
  "peerDependenciesMeta": {
280
282
  "@opentelemetry/auto-instrumentations-node": {
@@ -334,13 +336,13 @@
334
336
  "@opentelemetry/resource-detector-gcp": "^0.48.0",
335
337
  "@opentelemetry/sdk-logs": "^0.213.0",
336
338
  "@opentelemetry/sdk-trace-node": "^2.6.0",
337
- "@swc/core": "^1.15.18",
339
+ "@swc/core": "^1.15.21",
338
340
  "@total-typescript/ts-reset": "^0.6.1",
339
341
  "@total-typescript/tsconfig": "^1.0.4",
340
342
  "@types/eslint-config-prettier": "^6.11.3",
341
343
  "@types/node": "^25.5.0",
342
- "@typescript-eslint/eslint-plugin": "^8.57.0",
343
- "@typescript-eslint/parser": "^8.57.0",
344
+ "@typescript-eslint/eslint-plugin": "^8.57.1",
345
+ "@typescript-eslint/parser": "^8.57.1",
344
346
  "eslint-config-prettier": "^10.1.8",
345
347
  "eslint-plugin-unicorn": "^63.0.0",
346
348
  "pino": "^10.3.1",
@@ -349,13 +351,13 @@
349
351
  "tsup": "^8.5.1",
350
352
  "tsx": "^4.21.0",
351
353
  "typescript": "^5.9.3",
352
- "typescript-eslint": "^8.57.0",
354
+ "typescript-eslint": "^8.57.1",
353
355
  "unplugin-swc": "^1.5.9",
354
356
  "vite-tsconfig-paths": "^6.1.1",
355
357
  "vitest": "^4.1.0",
356
358
  "vitest-mock-extended": "^3.1.0",
357
359
  "winston": "^3.19.0",
358
- "yaml": "^2.8.2"
360
+ "yaml": "^2.8.3"
359
361
  },
360
362
  "repository": {
361
363
  "type": "git",
@@ -20,18 +20,24 @@ const mockedModules = [
20
20
  '@opentelemetry/sdk-metrics',
21
21
  ];
22
22
 
23
- // Mock instrumentation classes with exact names from OpenTelemetry
24
- class MongoDBInstrumentation {
23
+ // Mock instrumentation classes with exact names from OpenTelemetry.
24
+ // NodeSDK.start() calls lifecycle hooks on each instrumentation instance.
25
+ class MockInstrumentationBase {
25
26
  constructor(public config?: Record<string, unknown>) {}
26
- }
27
27
 
28
- class MongooseInstrumentation {
29
- constructor(public config?: Record<string, unknown>) {}
28
+ setConfig(_config: Record<string, unknown>) {}
29
+ setTracerProvider(_provider: unknown) {}
30
+ setMeterProvider(_provider: unknown) {}
31
+ setLoggerProvider(_provider: unknown) {}
32
+ enable() {}
33
+ disable() {}
30
34
  }
31
35
 
32
- class HttpInstrumentation {
33
- constructor(public config?: Record<string, unknown>) {}
34
- }
36
+ class MongoDBInstrumentation extends MockInstrumentationBase {}
37
+
38
+ class MongooseInstrumentation extends MockInstrumentationBase {}
39
+
40
+ class HttpInstrumentation extends MockInstrumentationBase {}
35
41
 
36
42
  async function loadInitWithMocks() {
37
43
  const sdkInstances: SdkRecord[] = [];
@@ -1,138 +1,36 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import type { NodeSDK } from '@opentelemetry/sdk-node';
3
- import { mock, mockDeep, type DeepMockProxy } from 'vitest-mock-extended';
4
-
5
- type SdkRecord = {
6
- options: Record<string, unknown>;
7
- instance: DeepMockProxy<NodeSDK>;
8
- };
9
-
10
- const mockedModules = [
11
- '@opentelemetry/sdk-node',
12
- '@opentelemetry/exporter-trace-otlp-http',
13
- '@opentelemetry/exporter-metrics-otlp-http',
14
- '@opentelemetry/sdk-metrics',
15
- './node-require',
16
- './node-require.ts',
17
- ];
18
-
19
- // Track traceloop initialize calls globally
20
- const traceloopInitializeCalls: Array<Record<string, unknown>> = [];
21
-
22
- // Store mock initialize function globally
23
- let globalMockInitialize: ReturnType<typeof vi.fn> | null = null;
24
-
25
- async function loadInitWithMocks() {
26
- const sdkInstances: SdkRecord[] = [];
27
-
28
- class MockNodeSDK {
29
- constructor(options: Record<string, unknown>) {
30
- const instance = mockDeep<NodeSDK>();
31
- instance.start.mockImplementation(() => {});
32
- instance.shutdown.mockResolvedValue();
33
-
34
- (instance as any).getTracerProvider = vi.fn().mockReturnValue(mock());
35
- sdkInstances.push({ options, instance });
36
- return instance;
37
- }
38
- }
39
-
40
- class MockOTLPTraceExporter {
41
- options: Record<string, unknown>;
42
-
43
- constructor(options: Record<string, unknown>) {
44
- this.options = options;
45
- }
46
- }
47
-
48
- class MockOTLPMetricExporter {
49
- options: Record<string, unknown>;
50
-
51
- constructor(options: Record<string, unknown>) {
52
- this.options = options;
53
- }
54
- }
55
-
56
- class MockPeriodicExportingMetricReader {
57
- options: Record<string, unknown>;
58
-
59
- constructor(options: Record<string, unknown>) {
60
- this.options = options;
61
- }
62
- }
63
-
64
- // Clear module cache first
65
- vi.resetModules();
66
-
67
- // Create mock function that captures calls and store it globally
68
- globalMockInitialize = vi.fn((options?: Record<string, unknown>) => {
69
- traceloopInitializeCalls.push(options || {});
70
- });
71
-
72
- const mockTraceloop = {
73
- initialize: globalMockInitialize,
74
- instrumentations: [{ name: 'openai' }, { name: 'langchain' }],
75
- };
76
-
77
- // Mock node-require to intercept safeRequire('@traceloop/node-server-sdk').
78
- // vi.doMock on the traceloop module itself doesn't work because safeRequire
79
- // uses native require() which bypasses vitest's module interception.
80
- const nodeRequireMock = () => ({
81
- safeRequire: vi.fn((id: string) => {
82
- if (id === '@traceloop/node-server-sdk') {
83
- return mockTraceloop;
84
- }
85
- return undefined;
86
- }),
87
- requireModule: vi.fn((id: string) => {
88
- const err = new Error(`Cannot find module '${id}'`);
89
- (err as NodeJS.ErrnoException).code = 'MODULE_NOT_FOUND';
90
- throw err;
91
- }),
92
- nodeRequire: vi.fn(),
93
- });
94
-
95
- vi.doMock('./node-require', nodeRequireMock);
96
- vi.doMock('./node-require.ts', nodeRequireMock);
97
-
98
- vi.doMock('@opentelemetry/sdk-node', () => ({
99
- NodeSDK: MockNodeSDK,
100
- }));
101
-
102
- vi.doMock('@opentelemetry/exporter-trace-otlp-http', () => ({
103
- OTLPTraceExporter: MockOTLPTraceExporter,
104
- }));
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
105
2
 
106
- vi.doMock('@opentelemetry/exporter-metrics-otlp-http', () => ({
107
- OTLPMetricExporter: MockOTLPMetricExporter,
108
- }));
3
+ type InitModule = typeof import('./init');
109
4
 
110
- vi.doMock('@opentelemetry/sdk-metrics', () => ({
111
- PeriodicExportingMetricReader: MockPeriodicExportingMetricReader,
112
- }));
5
+ async function loadInitModule(): Promise<InitModule> {
6
+ vi.resetModules();
7
+ return import('./init');
8
+ }
113
9
 
114
- const mod = await import('./init');
10
+ function createSdkFactory() {
11
+ const calls: Array<Record<string, unknown>> = [];
12
+ const getTracerProvider = vi.fn(() => ({ id: 'mock-tracer-provider' }));
13
+ const start = vi.fn();
14
+ const shutdown = vi.fn(async () => {});
115
15
 
116
16
  return {
117
- init: mod.init,
118
- getConfig: mod.getConfig,
119
- sdkInstances,
120
- traceloopInitializeCalls,
121
- mockTraceloop,
17
+ calls,
18
+ getTracerProvider,
19
+ start,
20
+ shutdown,
21
+ sdkFactory: (options: Record<string, unknown>) => {
22
+ calls.push(options);
23
+ return {
24
+ start,
25
+ shutdown,
26
+ getTracerProvider,
27
+ } as never;
28
+ },
122
29
  };
123
30
  }
124
31
 
125
32
  describe('init() OpenLLMetry integration', () => {
126
- beforeEach(() => {
127
- vi.resetModules();
128
- traceloopInitializeCalls.length = 0;
129
- globalMockInitialize = null;
130
- });
131
-
132
33
  afterEach(() => {
133
- for (const mod of mockedModules) {
134
- vi.doUnmock(mod);
135
- }
136
34
  vi.clearAllMocks();
137
35
  delete process.env.AUTOTEL_METRICS;
138
36
  delete process.env.NODE_ENV;
@@ -140,29 +38,53 @@ describe('init() OpenLLMetry integration', () => {
140
38
  });
141
39
 
142
40
  it('should not initialize OpenLLMetry when disabled', async () => {
143
- const { init, traceloopInitializeCalls } = await loadInitWithMocks();
41
+ const mod = await loadInitModule();
42
+ const sdk = createSdkFactory();
43
+ const traceloopInitializeCalls: Array<Record<string, unknown>> = [];
44
+
45
+ mod._setOptionalRequireForTesting(() => ({
46
+ initialize: (options?: Record<string, unknown>) =>
47
+ traceloopInitializeCalls.push(options ?? {}),
48
+ }));
144
49
 
145
- init({ service: 'test-app' });
50
+ mod.init({ service: 'test-app', sdkFactory: sdk.sdkFactory });
146
51
 
147
52
  expect(traceloopInitializeCalls).toHaveLength(0);
53
+ mod._resetOptionalRequireForTesting();
148
54
  });
149
55
 
150
56
  it('should initialize OpenLLMetry when enabled', async () => {
151
- const { init, traceloopInitializeCalls } = await loadInitWithMocks();
57
+ const mod = await loadInitModule();
58
+ const sdk = createSdkFactory();
59
+ const traceloopInitializeCalls: Array<Record<string, unknown>> = [];
60
+
61
+ mod._setOptionalRequireForTesting(() => ({
62
+ initialize: (options?: Record<string, unknown>) =>
63
+ traceloopInitializeCalls.push(options ?? {}),
64
+ }));
152
65
 
153
- init({
66
+ mod.init({
154
67
  service: 'test-app',
155
68
  openllmetry: { enabled: true },
69
+ sdkFactory: sdk.sdkFactory,
156
70
  });
157
71
 
158
72
  expect(traceloopInitializeCalls).toHaveLength(1);
159
73
  expect(traceloopInitializeCalls[0]).toBeDefined();
74
+ mod._resetOptionalRequireForTesting();
160
75
  });
161
76
 
162
77
  it('should pass OpenLLMetry options to initialize', async () => {
163
- const { init, traceloopInitializeCalls } = await loadInitWithMocks();
78
+ const mod = await loadInitModule();
79
+ const sdk = createSdkFactory();
80
+ const traceloopInitializeCalls: Array<Record<string, unknown>> = [];
81
+
82
+ mod._setOptionalRequireForTesting(() => ({
83
+ initialize: (options?: Record<string, unknown>) =>
84
+ traceloopInitializeCalls.push(options ?? {}),
85
+ }));
164
86
 
165
- init({
87
+ mod.init({
166
88
  service: 'test-app',
167
89
  openllmetry: {
168
90
  enabled: true,
@@ -171,6 +93,7 @@ describe('init() OpenLLMetry integration', () => {
171
93
  apiKey: 'test-key',
172
94
  },
173
95
  },
96
+ sdkFactory: sdk.sdkFactory,
174
97
  });
175
98
 
176
99
  expect(traceloopInitializeCalls).toHaveLength(1);
@@ -178,112 +101,94 @@ describe('init() OpenLLMetry integration', () => {
178
101
  disableBatch: true,
179
102
  apiKey: 'test-key',
180
103
  });
104
+ mod._resetOptionalRequireForTesting();
181
105
  });
182
106
 
183
107
  it('should reuse autotel tracer provider when OpenLLMetry is enabled', async () => {
184
- const { init, traceloopInitializeCalls, sdkInstances } =
185
- await loadInitWithMocks();
108
+ const mod = await loadInitModule();
109
+ const sdk = createSdkFactory();
110
+ const traceloopInitializeCalls: Array<Record<string, unknown>> = [];
186
111
 
187
- init({
112
+ mod._setOptionalRequireForTesting(() => ({
113
+ initialize: (options?: Record<string, unknown>) =>
114
+ traceloopInitializeCalls.push(options ?? {}),
115
+ }));
116
+
117
+ mod.init({
188
118
  service: 'test-app',
189
119
  openllmetry: { enabled: true },
120
+ sdkFactory: sdk.sdkFactory,
190
121
  });
191
122
 
192
- expect(sdkInstances).toHaveLength(1);
193
- const sdkInstance = sdkInstances[0].instance;
194
-
195
123
  expect(traceloopInitializeCalls).toHaveLength(1);
196
124
  const callOptions = traceloopInitializeCalls[0];
197
125
  expect(callOptions).toBeDefined();
198
- expect((sdkInstance as any).getTracerProvider).toHaveBeenCalled();
126
+ expect(sdk.getTracerProvider).toHaveBeenCalled();
127
+ mod._resetOptionalRequireForTesting();
199
128
  });
200
129
 
201
130
  it('should add OpenLLMetry instrumentations when selectiveInstrumentation is false', async () => {
202
- const { init, sdkInstances, mockTraceloop } = await loadInitWithMocks();
131
+ const mod = await loadInitModule();
132
+ const sdk = createSdkFactory();
133
+ const mockTraceloop = {
134
+ initialize: vi.fn(),
135
+ instrumentations: [{ name: 'openai' }, { name: 'langchain' }],
136
+ };
203
137
 
204
- init({
138
+ mod._setOptionalRequireForTesting(() => mockTraceloop);
139
+
140
+ mod.init({
205
141
  service: 'test-app',
206
142
  openllmetry: { enabled: true },
207
143
  autoInstrumentations: false,
144
+ sdkFactory: sdk.sdkFactory,
208
145
  });
209
146
 
210
- const options = sdkInstances.at(-1)?.options as Record<string, unknown>;
147
+ const options = sdk.calls.at(-1) as Record<string, unknown>;
211
148
  const instrumentations = options.instrumentations as unknown[];
212
149
 
213
150
  expect(instrumentations).toBeDefined();
214
151
  expect(mockTraceloop.instrumentations).toBeDefined();
152
+ mod._resetOptionalRequireForTesting();
215
153
  });
216
154
 
217
155
  it('should handle missing @traceloop/node-server-sdk gracefully', async () => {
218
- vi.resetModules();
219
-
220
- // Mock node-require to return undefined for traceloop (simulating not installed)
221
- const nodeRequireMock = () => ({
222
- safeRequire: vi.fn(() => undefined),
223
- requireModule: vi.fn((id: string) => {
224
- const err = new Error(`Cannot find module '${id}'`);
225
- (err as NodeJS.ErrnoException).code = 'MODULE_NOT_FOUND';
226
- throw err;
227
- }),
228
- nodeRequire: vi.fn(),
229
- });
230
-
231
- vi.doMock('./node-require', nodeRequireMock);
232
- vi.doMock('./node-require.ts', nodeRequireMock);
233
-
234
- vi.doMock('@opentelemetry/sdk-node', () => ({
235
- NodeSDK: class {
236
- constructor() {
237
- const instance = mockDeep<NodeSDK>();
238
- instance.start.mockImplementation(() => {});
239
- instance.shutdown.mockResolvedValue();
240
- return instance;
241
- }
242
- },
243
- }));
244
-
245
- vi.doMock('@opentelemetry/exporter-trace-otlp-http', () => ({
246
- OTLPTraceExporter: class {
247
- constructor() {}
248
- },
249
- }));
250
-
251
- vi.doMock('@opentelemetry/exporter-metrics-otlp-http', () => ({
252
- OTLPMetricExporter: class {
253
- constructor() {}
254
- },
255
- }));
256
-
257
- vi.doMock('@opentelemetry/sdk-metrics', () => ({
258
- PeriodicExportingMetricReader: class {
259
- constructor() {}
260
- },
261
- }));
262
-
263
- const { init } = await import('./init');
156
+ const mod = await loadInitModule();
157
+ const sdk = createSdkFactory();
158
+ mod._setOptionalRequireForTesting(() => undefined);
264
159
 
265
160
  expect(() => {
266
- init({
161
+ mod.init({
267
162
  service: 'test-app',
268
163
  openllmetry: { enabled: true },
164
+ sdkFactory: sdk.sdkFactory,
269
165
  });
270
166
  }).not.toThrow();
167
+ mod._resetOptionalRequireForTesting();
271
168
  });
272
169
 
273
170
  it('should initialize OpenLLMetry after SDK start', async () => {
274
- const { init, sdkInstances, traceloopInitializeCalls } =
275
- await loadInitWithMocks();
171
+ const mod = await loadInitModule();
172
+ const sdk = createSdkFactory();
173
+ const traceloopInitializeCalls: Array<Record<string, unknown>> = [];
174
+
175
+ mod._setOptionalRequireForTesting(() => ({
176
+ initialize: (options?: Record<string, unknown>) =>
177
+ traceloopInitializeCalls.push(options ?? {}),
178
+ }));
276
179
 
277
- init({
180
+ mod.init({
278
181
  service: 'test-app',
279
182
  openllmetry: { enabled: true },
183
+ sdkFactory: sdk.sdkFactory,
280
184
  });
281
185
 
282
186
  // Verify SDK started (synchronously in init)
283
- expect(sdkInstances).toHaveLength(1);
284
- expect(sdkInstances[0].instance.start).toHaveBeenCalled();
187
+ expect(sdk.calls).toHaveLength(1);
188
+ expect(sdk.start).toHaveBeenCalled();
285
189
 
286
190
  // Verify OpenLLMetry was initialized (synchronously via safeRequire)
287
191
  expect(traceloopInitializeCalls).toHaveLength(1);
192
+ mod._resetOptionalRequireForTesting();
288
193
  });
289
194
  });
package/src/init.ts CHANGED
@@ -1022,6 +1022,7 @@ let logger: Logger = silentLogger; // Silent by default - no spam
1022
1022
  let validationConfig: Partial<ValidationConfig> | null = null;
1023
1023
  let eventsConfig: EventsConfig | null = null;
1024
1024
  let _stringRedactor: StringRedactor | null = null;
1025
+ let _optionalRequire: typeof safeRequire = safeRequire;
1025
1026
 
1026
1027
  /**
1027
1028
  * Resolve metrics flag with env var override support
@@ -1471,7 +1472,7 @@ export function init(cfg: AutotelConfig): void {
1471
1472
 
1472
1473
  // Initialize OpenLLMetry if enabled (after SDK starts to reuse tracer provider)
1473
1474
  if (mergedConfig.openllmetry?.enabled) {
1474
- const traceloop = safeRequire<{
1475
+ const traceloop = _optionalRequire<{
1475
1476
  initialize?: (options?: Record<string, unknown>) => void;
1476
1477
  }>('@traceloop/node-server-sdk');
1477
1478
 
@@ -1833,6 +1834,22 @@ export function getStringRedactor(): StringRedactor | null {
1833
1834
  return _stringRedactor;
1834
1835
  }
1835
1836
 
1837
+ /**
1838
+ * @internal Override optional require for deterministic tests.
1839
+ */
1840
+ export function _setOptionalRequireForTesting(
1841
+ loader: typeof safeRequire,
1842
+ ): void {
1843
+ _optionalRequire = loader;
1844
+ }
1845
+
1846
+ /**
1847
+ * @internal Reset optional require override.
1848
+ */
1849
+ export function _resetOptionalRequireForTesting(): void {
1850
+ _optionalRequire = safeRequire;
1851
+ }
1852
+
1836
1853
  /**
1837
1854
  * Get SDK instance (for shutdown)
1838
1855
  */
@@ -124,7 +124,7 @@ function isSerializable(v: unknown): v is SerializableValue {
124
124
  return false;
125
125
  }
126
126
 
127
- function serializeSpan(span: ReadableSpan): SerializedSpan {
127
+ export function serializeSpan(span: ReadableSpan): SerializedSpan {
128
128
  const attrs: Record<string, SerializableValue> = {};
129
129
  for (const [k, v] of Object.entries(span.attributes)) {
130
130
  if (isSerializable(v)) attrs[k] = v;