weflayr 0.23.0 → 0.26.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 (225) hide show
  1. package/README.md +2 -2
  2. package/dist/{api → src/api}/generated/facade.gen.d.ts +163 -12
  3. package/dist/{api → src/api}/generated/facade.gen.js +10 -2
  4. package/dist/src/api/generated/index.d.ts +2 -0
  5. package/dist/{api → src/api}/generated/index.js +9 -1
  6. package/dist/{api → src/api}/generated/sdk.gen.d.ts +35 -3
  7. package/dist/{api → src/api}/generated/sdk.gen.js +88 -4
  8. package/{src/api/generated/types.gen.ts → dist/src/api/generated/types.gen.d.ts} +692 -116
  9. package/dist/{api → src/api}/makeEndpoint.js +1 -3
  10. package/dist/src/auto-instrument.d.ts +42 -0
  11. package/dist/src/auto-instrument.js +156 -0
  12. package/dist/src/index.d.ts +9 -0
  13. package/dist/src/index.js +49 -0
  14. package/dist/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.d.ts +20 -0
  15. package/dist/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.js +114 -0
  16. package/dist/src/instrumentation/anthropic/get_cache_config_telemetry.d.ts +28 -0
  17. package/dist/src/instrumentation/anthropic/get_cache_config_telemetry.js +92 -0
  18. package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.d.ts +25 -0
  19. package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.js +141 -0
  20. package/dist/src/instrumentation/anthropic/get_structured_output_schema_telemetry.d.ts +9 -0
  21. package/dist/src/instrumentation/anthropic/get_structured_output_schema_telemetry.js +17 -0
  22. package/dist/src/instrumentation/anthropic/get_tool_definitions_telemetry.d.ts +15 -0
  23. package/dist/src/instrumentation/anthropic/get_tool_definitions_telemetry.js +50 -0
  24. package/dist/src/instrumentation/anthropic/set_telemetry_attributes.d.ts +15 -0
  25. package/dist/src/instrumentation/anthropic/set_telemetry_attributes.js +39 -0
  26. package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.d.ts +22 -0
  27. package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.js +97 -0
  28. package/dist/src/instrumentation/bedrock/get_cache_telemetry.d.ts +22 -0
  29. package/dist/src/instrumentation/bedrock/get_cache_telemetry.js +102 -0
  30. package/dist/src/instrumentation/bedrock/get_input_message_content_telemetry.d.ts +15 -0
  31. package/dist/src/instrumentation/bedrock/get_input_message_content_telemetry.js +91 -0
  32. package/dist/src/instrumentation/bedrock/get_model_id_telemetry.d.ts +21 -0
  33. package/dist/src/instrumentation/bedrock/get_model_id_telemetry.js +54 -0
  34. package/dist/src/instrumentation/bedrock/get_reasoning_output_telemetry.d.ts +19 -0
  35. package/dist/src/instrumentation/bedrock/get_reasoning_output_telemetry.js +81 -0
  36. package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.d.ts +27 -0
  37. package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +126 -0
  38. package/dist/src/instrumentation/bedrock/get_tool_call_output_telemetry.d.ts +17 -0
  39. package/dist/src/instrumentation/bedrock/get_tool_call_output_telemetry.js +85 -0
  40. package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.d.ts +15 -0
  41. package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.js +68 -0
  42. package/dist/src/instrumentation/bedrock/set_telemetry_attributes.d.ts +18 -0
  43. package/dist/src/instrumentation/bedrock/set_telemetry_attributes.js +47 -0
  44. package/dist/src/instrumentation/cohere/get_chat_telemetry.d.ts +23 -0
  45. package/dist/src/instrumentation/cohere/get_chat_telemetry.js +77 -0
  46. package/dist/src/instrumentation/cohere/get_embeddings_telemetry.d.ts +18 -0
  47. package/dist/src/instrumentation/cohere/get_embeddings_telemetry.js +88 -0
  48. package/dist/src/instrumentation/cohere/get_structured_output_schema_telemetry.d.ts +12 -0
  49. package/dist/src/instrumentation/cohere/get_structured_output_schema_telemetry.js +18 -0
  50. package/dist/src/instrumentation/cohere/get_tool_call_output_telemetry.d.ts +31 -0
  51. package/dist/src/instrumentation/cohere/get_tool_call_output_telemetry.js +106 -0
  52. package/dist/src/instrumentation/cohere/get_tool_definitions_telemetry.d.ts +27 -0
  53. package/dist/src/instrumentation/cohere/get_tool_definitions_telemetry.js +75 -0
  54. package/dist/src/instrumentation/cohere/set_telemetry_attributes.d.ts +17 -0
  55. package/dist/src/instrumentation/cohere/set_telemetry_attributes.js +44 -0
  56. package/dist/src/instrumentation/elevenlabs/full_instrumentation.d.ts +28 -0
  57. package/dist/src/instrumentation/elevenlabs/full_instrumentation.js +209 -0
  58. package/dist/src/instrumentation/google/get_audio_telemetry.d.ts +9 -0
  59. package/dist/src/instrumentation/google/get_audio_telemetry.js +28 -0
  60. package/dist/src/instrumentation/google/get_cache_operations_telemetry.d.ts +23 -0
  61. package/dist/src/instrumentation/google/get_cache_operations_telemetry.js +154 -0
  62. package/dist/src/instrumentation/google/get_cache_telemetry.d.ts +16 -0
  63. package/dist/src/instrumentation/google/get_cache_telemetry.js +38 -0
  64. package/dist/src/instrumentation/google/get_embeddings_telemetry.d.ts +12 -0
  65. package/dist/src/instrumentation/google/get_embeddings_telemetry.js +85 -0
  66. package/dist/src/instrumentation/google/get_provider_name.d.ts +11 -0
  67. package/{src → dist/src}/instrumentation/google/get_provider_name.js +11 -12
  68. package/{src/instrumentation/google/get_reasoning_telemetry.js → dist/src/instrumentation/google/get_reasoning_telemetry.d.ts} +1 -11
  69. package/dist/src/instrumentation/google/get_reasoning_telemetry.js +21 -0
  70. package/dist/src/instrumentation/google/get_structured_output_schema_telemetry.d.ts +12 -0
  71. package/dist/src/instrumentation/google/get_structured_output_schema_telemetry.js +19 -0
  72. package/dist/src/instrumentation/google/set_telemetry_attributes.d.ts +18 -0
  73. package/dist/src/instrumentation/google/set_telemetry_attributes.js +98 -0
  74. package/dist/src/instrumentation/openai/get_audio_telemetry.d.ts +25 -0
  75. package/dist/src/instrumentation/openai/get_audio_telemetry.js +123 -0
  76. package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.d.ts +35 -0
  77. package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.js +148 -0
  78. package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.d.ts +27 -0
  79. package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +72 -0
  80. package/dist/src/instrumentation/openai/get_cache_config_telemetry.d.ts +28 -0
  81. package/dist/src/instrumentation/openai/get_cache_config_telemetry.js +91 -0
  82. package/dist/src/instrumentation/openai/get_embeddings_telemetry.d.ts +18 -0
  83. package/dist/src/instrumentation/openai/get_embeddings_telemetry.js +86 -0
  84. package/dist/src/instrumentation/openai/get_streaming_usage.d.ts +21 -0
  85. package/dist/src/instrumentation/openai/get_streaming_usage.js +74 -0
  86. package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.d.ts +12 -0
  87. package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.js +20 -0
  88. package/dist/src/instrumentation/openai/set_telemetry_attributes.d.ts +17 -0
  89. package/dist/src/instrumentation/openai/set_telemetry_attributes.js +47 -0
  90. package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.d.ts +35 -0
  91. package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.js +76 -0
  92. package/dist/src/instrumentation/vercel/full_instrumentation.d.ts +3 -0
  93. package/dist/src/instrumentation/vercel/full_instrumentation.js +54 -0
  94. package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.d.ts +22 -0
  95. package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.js +66 -0
  96. package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.d.ts +24 -0
  97. package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.js +84 -0
  98. package/dist/src/logger.d.ts +1 -0
  99. package/dist/src/logger.js +6 -0
  100. package/dist/src/otel/exporter.d.ts +19 -0
  101. package/dist/src/otel/exporter.js +94 -0
  102. package/dist/src/otel/propagation.d.ts +46 -0
  103. package/dist/src/otel/propagation.js +110 -0
  104. package/dist/src/otel/span-filter.d.ts +3 -0
  105. package/dist/src/otel/span-filter.js +61 -0
  106. package/dist/src/otel/span-processor.d.ts +55 -0
  107. package/dist/src/otel/span-processor.js +169 -0
  108. package/dist/src/otel/vercel-ai-sdk-span-filter.d.ts +12 -0
  109. package/dist/src/otel/vercel-ai-sdk-span-filter.js +88 -0
  110. package/dist/src/setup.d.ts +50 -0
  111. package/dist/src/setup.js +155 -0
  112. package/package.json +11 -5
  113. package/.prettierignore +0 -2
  114. package/.prettierrc.json +0 -7
  115. package/LICENSE-MIT +0 -21
  116. package/NOTICE +0 -12
  117. package/dist/api/generated/index.d.ts +0 -2
  118. package/dist/api/generated/types.gen.d.ts +0 -749
  119. package/eslint.config.mjs +0 -36
  120. package/index.d.ts +0 -167
  121. package/openapi-ts.config.mjs +0 -9
  122. package/scripts/generate_facade.mjs +0 -86
  123. package/src/api/caseConversion.ts +0 -58
  124. package/src/api/generated/client/client.gen.ts +0 -277
  125. package/src/api/generated/client/index.ts +0 -27
  126. package/src/api/generated/client/types.gen.ts +0 -218
  127. package/src/api/generated/client/utils.gen.ts +0 -316
  128. package/src/api/generated/client.gen.ts +0 -16
  129. package/src/api/generated/core/auth.gen.ts +0 -48
  130. package/src/api/generated/core/bodySerializer.gen.ts +0 -82
  131. package/src/api/generated/core/params.gen.ts +0 -178
  132. package/src/api/generated/core/pathSerializer.gen.ts +0 -171
  133. package/src/api/generated/core/queryKeySerializer.gen.ts +0 -117
  134. package/src/api/generated/core/serverSentEvents.gen.ts +0 -242
  135. package/src/api/generated/core/types.gen.ts +0 -110
  136. package/src/api/generated/core/utils.gen.ts +0 -140
  137. package/src/api/generated/facade.gen.ts +0 -68
  138. package/src/api/generated/index.ts +0 -4
  139. package/src/api/generated/sdk.gen.ts +0 -121
  140. package/src/api/index.ts +0 -45
  141. package/src/api/makeEndpoint.ts +0 -54
  142. package/src/auto-instrument.js +0 -171
  143. package/src/index.js +0 -19
  144. package/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.js +0 -107
  145. package/src/instrumentation/anthropic/get_streaming_stop_reason_telemetry.js +0 -121
  146. package/src/instrumentation/anthropic/get_tool_definitions_telemetry.js +0 -50
  147. package/src/instrumentation/anthropic/set_telemetry_attributes.js +0 -37
  148. package/src/instrumentation/bedrock/get_cache_telemetry.js +0 -104
  149. package/src/instrumentation/bedrock/get_input_message_content_telemetry.js +0 -94
  150. package/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +0 -87
  151. package/src/instrumentation/bedrock/set_telemetry_attributes.js +0 -36
  152. package/src/instrumentation/cohere/get_chat_telemetry.js +0 -88
  153. package/src/instrumentation/cohere/get_embeddings_telemetry.js +0 -107
  154. package/src/instrumentation/cohere/set_telemetry_attributes.js +0 -32
  155. package/src/instrumentation/elevenlabs/full_instrumentation.js +0 -236
  156. package/src/instrumentation/google/get_audio_telemetry.js +0 -30
  157. package/src/instrumentation/google/get_cache_telemetry.js +0 -25
  158. package/src/instrumentation/google/get_embeddings_telemetry.js +0 -99
  159. package/src/instrumentation/google/set_telemetry_attributes.js +0 -98
  160. package/src/instrumentation/openai/get_audio_telemetry.js +0 -149
  161. package/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +0 -71
  162. package/src/instrumentation/openai/get_embeddings_telemetry.js +0 -107
  163. package/src/instrumentation/openai/get_streaming_usage.js +0 -81
  164. package/src/instrumentation/openai/set_telemetry_attributes.js +0 -39
  165. package/src/instrumentation/vercel/full_instrumentation.js +0 -44
  166. package/src/logger.js +0 -8
  167. package/src/otel/exporter.js +0 -108
  168. package/src/otel/propagation.js +0 -133
  169. package/src/otel/span-filter.js +0 -69
  170. package/src/otel/span-processor.js +0 -162
  171. package/src/otel/vercel-ai-sdk-span-filter.js +0 -79
  172. package/src/setup.js +0 -167
  173. package/tests/api/index.test.js +0 -437
  174. package/tests/index.test.js +0 -824
  175. package/tests/instrumentation/anthropic/get_cache_and_reasoning_telemetry.test.js +0 -158
  176. package/tests/instrumentation/anthropic/get_streaming_stop_reason_telemetry.test.js +0 -117
  177. package/tests/instrumentation/anthropic/get_tool_definitions_telemetry.test.js +0 -82
  178. package/tests/instrumentation/bedrock/get_cache_telemetry.test.js +0 -102
  179. package/tests/instrumentation/bedrock/get_input_message_content_telemetry.test.js +0 -89
  180. package/tests/instrumentation/bedrock/get_streaming_output_message_telemetry.test.js +0 -87
  181. package/tests/instrumentation/cohere/get_chat_telemetry.test.js +0 -148
  182. package/tests/instrumentation/cohere/get_embeddings_telemetry.test.js +0 -132
  183. package/tests/instrumentation/elevenlabs/full_instrumentation.test.js +0 -258
  184. package/tests/instrumentation/google/get_embeddings_telemetry.test.js +0 -128
  185. package/tests/instrumentation/google/get_reasoning_telemetry.test.js +0 -41
  186. package/tests/instrumentation/google/set_telemetry_attributes.test.js +0 -219
  187. package/tests/instrumentation/openai/get_audio_telemetry.test.js +0 -170
  188. package/tests/instrumentation/openai/get_cache_and_reasoning_telemetry.test.js +0 -83
  189. package/tests/instrumentation/openai/get_embeddings_telemetry.test.js +0 -145
  190. package/tests/instrumentation/openai/get_streaming_usage.test.js +0 -105
  191. package/tests/instrumentation/vercel/full_instrumentation.test.js +0 -16
  192. package/tests/otel/vercel-ai-sdk-span-filter.test.js +0 -159
  193. package/tsconfig.json +0 -18
  194. /package/dist/{api → src/api}/caseConversion.d.ts +0 -0
  195. /package/dist/{api → src/api}/caseConversion.js +0 -0
  196. /package/dist/{api → src/api}/generated/client/client.gen.d.ts +0 -0
  197. /package/dist/{api → src/api}/generated/client/client.gen.js +0 -0
  198. /package/dist/{api → src/api}/generated/client/index.d.ts +0 -0
  199. /package/dist/{api → src/api}/generated/client/index.js +0 -0
  200. /package/dist/{api → src/api}/generated/client/types.gen.d.ts +0 -0
  201. /package/dist/{api → src/api}/generated/client/types.gen.js +0 -0
  202. /package/dist/{api → src/api}/generated/client/utils.gen.d.ts +0 -0
  203. /package/dist/{api → src/api}/generated/client/utils.gen.js +0 -0
  204. /package/dist/{api → src/api}/generated/client.gen.d.ts +0 -0
  205. /package/dist/{api → src/api}/generated/client.gen.js +0 -0
  206. /package/dist/{api → src/api}/generated/core/auth.gen.d.ts +0 -0
  207. /package/dist/{api → src/api}/generated/core/auth.gen.js +0 -0
  208. /package/dist/{api → src/api}/generated/core/bodySerializer.gen.d.ts +0 -0
  209. /package/dist/{api → src/api}/generated/core/bodySerializer.gen.js +0 -0
  210. /package/dist/{api → src/api}/generated/core/params.gen.d.ts +0 -0
  211. /package/dist/{api → src/api}/generated/core/params.gen.js +0 -0
  212. /package/dist/{api → src/api}/generated/core/pathSerializer.gen.d.ts +0 -0
  213. /package/dist/{api → src/api}/generated/core/pathSerializer.gen.js +0 -0
  214. /package/dist/{api → src/api}/generated/core/queryKeySerializer.gen.d.ts +0 -0
  215. /package/dist/{api → src/api}/generated/core/queryKeySerializer.gen.js +0 -0
  216. /package/dist/{api → src/api}/generated/core/serverSentEvents.gen.d.ts +0 -0
  217. /package/dist/{api → src/api}/generated/core/serverSentEvents.gen.js +0 -0
  218. /package/dist/{api → src/api}/generated/core/types.gen.d.ts +0 -0
  219. /package/dist/{api → src/api}/generated/core/types.gen.js +0 -0
  220. /package/dist/{api → src/api}/generated/core/utils.gen.d.ts +0 -0
  221. /package/dist/{api → src/api}/generated/core/utils.gen.js +0 -0
  222. /package/dist/{api → src/api}/generated/types.gen.js +0 -0
  223. /package/dist/{api → src/api}/index.d.ts +0 -0
  224. /package/dist/{api → src/api}/index.js +0 -0
  225. /package/dist/{api → src/api}/makeEndpoint.d.ts +0 -0
@@ -1,824 +0,0 @@
1
- 'use strict';
2
-
3
- const test = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const Module = require('node:module');
6
-
7
- const { trace, context: otelContext } = require('@opentelemetry/api');
8
- const { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base');
9
- const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
10
- const { AsyncHooksContextManager } = require('@opentelemetry/context-async-hooks');
11
-
12
- const weflayr = require('../src');
13
- const { _state } = require('../src/setup');
14
- const { isDefaultExportSpan } = require('../src/otel/span-filter');
15
- const { WeflayrSpanProcessor } = require('../src/otel/span-processor');
16
- const { buildExporter, _exportWithRetry } = require('../src/otel/exporter');
17
-
18
- const contextManager = new AsyncHooksContextManager();
19
- contextManager.enable();
20
- otelContext.setGlobalContextManager(contextManager);
21
-
22
- // ── Helpers ───────────────────────────────────────────────────────────────────
23
-
24
- function makeExporterStub() {
25
- const exported = [];
26
- return {
27
- exported,
28
- export(spans, cb) {
29
- exported.push(...spans);
30
- cb({ code: 0 });
31
- },
32
- shutdown() {
33
- return Promise.resolve();
34
- },
35
- forceFlush() {
36
- return Promise.resolve();
37
- },
38
- };
39
- }
40
-
41
- function makeFakeSpan({
42
- attributes = {},
43
- instrumentationScope = { name: 'opentelemetry.instrumentation.openai' },
44
- name = 'openai.chat',
45
- } = {}) {
46
- const attrs = { ...attributes };
47
- const ctx = {
48
- traceId: '11111111111111111111111111111111',
49
- spanId: '2222222222222222',
50
- traceFlags: 1,
51
- };
52
- return {
53
- name,
54
- attributes: attrs,
55
- instrumentationScope,
56
- instrumentationLibrary: instrumentationScope,
57
- resource: { attributes: {} },
58
- kind: 0,
59
- status: { code: 0 },
60
- startTime: [0, 0],
61
- endTime: [0, 0],
62
- duration: [0, 0],
63
- events: [],
64
- links: [],
65
- droppedAttributesCount: 0,
66
- droppedEventsCount: 0,
67
- droppedLinksCount: 0,
68
- ended: true,
69
- spanContext: () => ctx,
70
- setAttribute(k, v) {
71
- attrs[k] = v;
72
- },
73
- setAttributes(obj) {
74
- Object.assign(attrs, obj);
75
- },
76
- isRecording: () => true,
77
- };
78
- }
79
-
80
- // ── setup ─────────────────────────────────────────────────────────────────────
81
-
82
- test('setup throws without apiKey', () => {
83
- const saved = process.env.WEFLAYR_API_KEY;
84
- delete process.env.WEFLAYR_API_KEY;
85
- try {
86
- assert.throws(() => weflayr.setup({}), /apiKey/);
87
- } finally {
88
- if (saved !== undefined) process.env.WEFLAYR_API_KEY = saved;
89
- }
90
- });
91
-
92
- test('setup picks up apiKey from WEFLAYR_API_KEY env var', () => {
93
- trace.disable();
94
- _state.processor = null;
95
- process.env.WEFLAYR_API_KEY = 'env-key';
96
- try {
97
- weflayr.setup({});
98
- assert.ok(_state.processor, 'processor must be installed using the env-var key');
99
- } finally {
100
- delete process.env.WEFLAYR_API_KEY;
101
- }
102
- });
103
-
104
- test('setup sets the content-capture env var when captureMessageContent=false', () => {
105
- delete process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT;
106
- weflayr.setup({ apiKey: 'test-key', captureMessageContent: false });
107
- assert.equal(process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, 'false');
108
- });
109
-
110
- test('setup with captureMessageContent=true sets env var to true', () => {
111
- delete process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT;
112
- weflayr.setup({ apiKey: 'test-key', captureMessageContent: true });
113
- assert.equal(process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, 'true');
114
- });
115
-
116
- test('setup without captureMessageContent leaves env var untouched', () => {
117
- delete process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT;
118
- weflayr.setup({ apiKey: 'test-key' });
119
- assert.equal(process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, undefined);
120
- });
121
-
122
- test('setup adopts a pre-existing TracerProvider', () => {
123
- // Reset the global proxy and our _state so the test owns the registry.
124
- trace.disable();
125
- _state.processor = null;
126
-
127
- // The user (or another OTel instrumentation) registers a provider first.
128
- const foreign = new NodeTracerProvider({ spanProcessors: [] });
129
- foreign.register();
130
-
131
- weflayr.setup({ apiKey: 'test-key' });
132
-
133
- // weflayr must have attached its processor to `foreign`, not created a new
134
- // provider — otherwise provider.register() would have silently no-op'd and
135
- // the LLM spans wouldn't reach our exporter.
136
- const attached = foreign._activeSpanProcessor?._spanProcessors ?? [];
137
- assert.ok(
138
- attached.some((sp) => sp instanceof WeflayrSpanProcessor),
139
- 'WeflayrSpanProcessor must be attached to the existing TracerProvider'
140
- );
141
- assert.strictEqual(_state.tracerProvider, foreign);
142
- });
143
-
144
- test('setup called twice replaces the prior processor', () => {
145
- // Stacking would double-export every span and leak HTTP pools in
146
- // long-running processes that reconfigure (e.g. API-key rotation).
147
- trace.disable();
148
- _state.processor = null;
149
-
150
- weflayr.setup({ apiKey: 'k1' });
151
- const firstProcessor = _state.processor;
152
- let shutdownCalled = false;
153
- const originalShutdown = firstProcessor.shutdown.bind(firstProcessor);
154
- firstProcessor.shutdown = () => {
155
- shutdownCalled = true;
156
- return originalShutdown();
157
- };
158
-
159
- weflayr.setup({ apiKey: 'k2' });
160
-
161
- assert.ok(_state.processor);
162
- assert.notStrictEqual(_state.processor, firstProcessor);
163
- assert.equal(shutdownCalled, true);
164
-
165
- const tp = _state.tracerProvider;
166
- const attached = tp._activeSpanProcessor?._spanProcessors ?? [];
167
- assert.ok(!attached.includes(firstProcessor));
168
- assert.ok(attached.includes(_state.processor));
169
- });
170
-
171
- // ── buildExporter ─────────────────────────────────────────────────────────────
172
-
173
- function _exporterParams(exporter) {
174
- return exporter._delegate._transport._transport._parameters;
175
- }
176
-
177
- test('buildExporter sets the bearer header and base URL', () => {
178
- const exporter = buildExporter({ baseUrl: 'https://api.weflayr.com', apiKey: 'sk-x' });
179
- assert.equal(_exporterParams(exporter).url, 'https://api.weflayr.com/');
180
- });
181
-
182
- test('buildExporter strips trailing slash on baseUrl', () => {
183
- const exporter = buildExporter({ baseUrl: 'https://api.weflayr.com/', apiKey: 'k' });
184
- assert.equal(_exporterParams(exporter).url, 'https://api.weflayr.com/');
185
- });
186
-
187
- test('buildExporter warns on every export error when warnOnExportError is true', async () => {
188
- const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
189
- const origExport = OTLPTraceExporter.prototype.export;
190
- OTLPTraceExporter.prototype.export = function (spans, cb) {
191
- cb({ code: 1, error: { code: 401, message: 'Unauthorized' } });
192
- };
193
- const warnings = [];
194
- const origWarn = console.warn;
195
- console.warn = (...args) => warnings.push(args.join(' '));
196
- try {
197
- const exporter = buildExporter({
198
- baseUrl: 'https://api.weflayr.com',
199
- apiKey: 'bad',
200
- warnOnExportError: true,
201
- });
202
- await new Promise((resolve) => exporter.export([], resolve));
203
- await new Promise((resolve) => exporter.export([], resolve));
204
- assert.equal(warnings.filter((w) => w.includes('[weflayr]')).length, 2);
205
- } finally {
206
- OTLPTraceExporter.prototype.export = origExport;
207
- console.warn = origWarn;
208
- }
209
- });
210
-
211
- test('buildExporter does not warn when warnOnExportError is false', async () => {
212
- const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
213
- const origExport = OTLPTraceExporter.prototype.export;
214
- OTLPTraceExporter.prototype.export = function (spans, cb) {
215
- cb({ code: 1, error: { code: 401, message: 'Unauthorized' } });
216
- };
217
- const warnings = [];
218
- const origWarn = console.warn;
219
- console.warn = (...args) => warnings.push(args.join(' '));
220
- try {
221
- const exporter = buildExporter({
222
- baseUrl: 'https://api.weflayr.com',
223
- apiKey: 'bad',
224
- warnOnExportError: false,
225
- });
226
- await new Promise((resolve) => exporter.export([], resolve));
227
- await new Promise((resolve) => exporter.export([], resolve));
228
- assert.equal(warnings.filter((w) => w.includes('[weflayr]')).length, 0);
229
- } finally {
230
- OTLPTraceExporter.prototype.export = origExport;
231
- console.warn = origWarn;
232
- }
233
- });
234
-
235
- // ── span-filter ───────────────────────────────────────────────────────────────
236
-
237
- test('isDefaultExportSpan accepts spans with gen_ai.* attributes', () => {
238
- const span = makeFakeSpan({
239
- attributes: { 'gen_ai.system': 'openai' },
240
- instrumentationScope: { name: 'unknown' },
241
- });
242
- assert.equal(isDefaultExportSpan(span), true);
243
- });
244
-
245
- test('isDefaultExportSpan accepts spans from known instrumentation scopes', () => {
246
- const span = makeFakeSpan({
247
- attributes: {},
248
- instrumentationScope: { name: 'opentelemetry.instrumentation.openai' },
249
- });
250
- assert.equal(isDefaultExportSpan(span), true);
251
- });
252
-
253
- test('isDefaultExportSpan rejects unrelated spans', () => {
254
- const span = makeFakeSpan({
255
- attributes: { 'http.method': 'GET' },
256
- instrumentationScope: { name: 'http' },
257
- });
258
- assert.equal(isDefaultExportSpan(span), false);
259
- });
260
-
261
- // ── WeflayrSpanProcessor ──────────────────────────────────────────────────────
262
-
263
- test('WeflayrSpanProcessor stamps defaultTags on span start', () => {
264
- const exporter = makeExporterStub();
265
- const proc = new WeflayrSpanProcessor({
266
- exporter,
267
- defaultTags: { app: 'svc-A', env: 'prod' },
268
- });
269
- const span = makeFakeSpan();
270
- proc.onStart(span, otelContext.active());
271
- assert.equal(span.attributes['weflayr.tag.app'], 'svc-A');
272
- assert.equal(span.attributes['weflayr.tag.env'], 'prod');
273
- });
274
-
275
- test('WeflayrSpanProcessor forwards GenAI spans to the exporter', async () => {
276
- const exporter = makeExporterStub();
277
- const proc = new WeflayrSpanProcessor({ exporter, exportMode: 'immediate' });
278
- const span = makeFakeSpan({ attributes: { 'gen_ai.system': 'openai' } });
279
- proc.onEnd(span);
280
- await proc.forceFlush();
281
- assert.equal(exporter.exported.length, 1);
282
- });
283
-
284
- test('WeflayrSpanProcessor drops non-GenAI spans', async () => {
285
- const exporter = makeExporterStub();
286
- const proc = new WeflayrSpanProcessor({ exporter, exportMode: 'immediate' });
287
- proc.onEnd(
288
- makeFakeSpan({
289
- attributes: { 'http.method': 'GET' },
290
- instrumentationScope: { name: 'http' },
291
- })
292
- );
293
- await proc.forceFlush();
294
- assert.equal(exporter.exported.length, 0);
295
- });
296
-
297
- test('WeflayrSpanProcessor ends orphaned LLM spans on forceFlush', async () => {
298
- // LLM spans that started but never ended should be force-ended with ERROR on
299
- // flush — covers openllmetry-style leaks (e.g. anthropic streaming auth error).
300
- const exporter = makeExporterStub();
301
- const proc = new WeflayrSpanProcessor({ exporter, exportMode: 'immediate' });
302
- const provider = new BasicTracerProvider({ spanProcessors: [proc] });
303
- const tracer = provider.getTracer('opentelemetry.instrumentation.anthropic');
304
-
305
- const leaked = tracer.startSpan('anthropic.chat');
306
- leaked.setAttribute('gen_ai.system', 'anthropic');
307
- assert.equal(exporter.exported.length, 0, 'span should not have been exported yet');
308
-
309
- await proc.forceFlush();
310
-
311
- assert.equal(exporter.exported.length, 1);
312
- // SpanStatusCode.ERROR === 2 in @opentelemetry/api
313
- assert.equal(exporter.exported[0].status.code, 2);
314
- await provider.shutdown();
315
- });
316
-
317
- test('WeflayrSpanProcessor applies mask before export', async () => {
318
- const exporter = makeExporterStub();
319
- let received = null;
320
- const proc = new WeflayrSpanProcessor({
321
- exporter,
322
- exportMode: 'immediate',
323
- mask: (attrs) => {
324
- received = attrs;
325
- attrs['gen_ai.prompt'] = '[REDACTED]';
326
- return attrs;
327
- },
328
- });
329
- const span = makeFakeSpan({
330
- attributes: { 'gen_ai.system': 'openai', 'gen_ai.prompt': 'Hi Noe' },
331
- });
332
- proc.onEnd(span);
333
- await proc.forceFlush();
334
- assert.ok(received);
335
- assert.equal(span.attributes['gen_ai.prompt'], '[REDACTED]');
336
- });
337
-
338
- test('WeflayrSpanProcessor strips message content when capture disabled', async () => {
339
- // Instrumentors gate content on their own env vars (openllmetry captures by
340
- // default), so the processor itself must drop message content when
341
- // captureMessageContent=false recorded "false" in the env var.
342
- process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = 'false';
343
- try {
344
- const exporter = makeExporterStub();
345
- const proc = new WeflayrSpanProcessor({ exporter, exportMode: 'immediate' });
346
- const span = makeFakeSpan({
347
- attributes: {
348
- 'gen_ai.system': 'openai',
349
- 'gen_ai.input.messages': '[{"role": "user"}]',
350
- 'gen_ai.output.messages': '[{"role": "assistant"}]',
351
- 'gen_ai.system_instructions': '[{"type": "text"}]',
352
- 'gen_ai.prompt.0.content': 'Hi Noe',
353
- 'gen_ai.completion.0.content': 'Hello!',
354
- 'gen_ai.usage.input_tokens': 12,
355
- },
356
- });
357
- proc.onEnd(span);
358
- await proc.forceFlush();
359
- assert.equal(span.attributes['gen_ai.system'], 'openai');
360
- assert.equal(span.attributes['gen_ai.usage.input_tokens'], 12);
361
- for (const key of Object.keys(span.attributes)) {
362
- assert.ok(!key.startsWith('gen_ai.prompt'), `unexpected ${key}`);
363
- assert.ok(!key.startsWith('gen_ai.completion'), `unexpected ${key}`);
364
- }
365
- assert.ok(!('gen_ai.input.messages' in span.attributes));
366
- assert.ok(!('gen_ai.output.messages' in span.attributes));
367
- assert.ok(!('gen_ai.system_instructions' in span.attributes));
368
- } finally {
369
- delete process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT;
370
- }
371
- });
372
-
373
- test('WeflayrSpanProcessor keeps message content when capture enabled', async () => {
374
- process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = 'true';
375
- try {
376
- const exporter = makeExporterStub();
377
- const proc = new WeflayrSpanProcessor({ exporter, exportMode: 'immediate' });
378
- const span = makeFakeSpan({
379
- attributes: {
380
- 'gen_ai.system': 'openai',
381
- 'gen_ai.input.messages': '[{"role": "user"}]',
382
- },
383
- });
384
- proc.onEnd(span);
385
- await proc.forceFlush();
386
- assert.equal(span.attributes['gen_ai.input.messages'], '[{"role": "user"}]');
387
- } finally {
388
- delete process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT;
389
- }
390
- });
391
-
392
- // ── propagateMetadata ────────────────────────────────────────────────────────
393
-
394
- function _makeMetadataHarness() {
395
- const exporter = makeExporterStub();
396
- const proc = new WeflayrSpanProcessor({ exporter, exportMode: 'immediate' });
397
- const provider = new BasicTracerProvider({ spanProcessors: [proc] });
398
- const tracer = provider.getTracer('test');
399
- return { exporter, proc, provider, tracer };
400
- }
401
-
402
- test('propagateMetadata writes structured fields and extra tags', async () => {
403
- const { exporter, proc, provider, tracer } = _makeMetadataHarness();
404
-
405
- await weflayr.propagateMetadata(
406
- {
407
- featureName: 'onboarding',
408
- customerName: '#3756',
409
- extraTags: { variant: 'control' },
410
- },
411
- async () => {
412
- const span = tracer.startSpan('child');
413
- span.setAttribute('gen_ai.system', 'openai');
414
- span.end();
415
- }
416
- );
417
-
418
- await proc.forceFlush();
419
- assert.equal(exporter.exported.length, 1);
420
- const attrs = exporter.exported[0].attributes;
421
- assert.equal(attrs['weflayr.feature_name'], 'onboarding');
422
- assert.equal(attrs['weflayr.customer_name'], '#3756');
423
- assert.equal(attrs['weflayr.tag.variant'], 'control');
424
- assert.ok(attrs['weflayr.uuid']);
425
- assert.equal(attrs['weflayr.provider_name'], undefined);
426
- await provider.shutdown();
427
- });
428
-
429
- test('propagateMetadata uuid is stable within scope and changes across calls', async () => {
430
- const { exporter, proc, provider, tracer } = _makeMetadataHarness();
431
-
432
- await weflayr.propagateMetadata({ featureName: 'f', customerName: 'c' }, async () => {
433
- const spanA = tracer.startSpan('a');
434
- spanA.setAttribute('gen_ai.system', 'openai');
435
- spanA.end();
436
- const spanB = tracer.startSpan('b');
437
- spanB.setAttribute('gen_ai.system', 'openai');
438
- spanB.end();
439
- });
440
-
441
- await weflayr.propagateMetadata({ featureName: 'f', customerName: 'c' }, async () => {
442
- const spanC = tracer.startSpan('c_span');
443
- spanC.setAttribute('gen_ai.system', 'openai');
444
- spanC.end();
445
- });
446
-
447
- await proc.forceFlush();
448
- const uuidA = exporter.exported[0].attributes['weflayr.uuid'];
449
- const uuidB = exporter.exported[1].attributes['weflayr.uuid'];
450
- const uuidC = exporter.exported[2].attributes['weflayr.uuid'];
451
- assert.equal(uuidA, uuidB);
452
- assert.notEqual(uuidA, uuidC);
453
- await provider.shutdown();
454
- });
455
-
456
- test('propagateMetadata nested scopes inner overrides and extra tags merge', async () => {
457
- const { exporter, proc, provider, tracer } = _makeMetadataHarness();
458
-
459
- await weflayr.propagateMetadata(
460
- {
461
- featureName: 'outer',
462
- customerName: 'outer_c',
463
- extraTags: { variant: 'A', shared: 'outer' },
464
- },
465
- async () => {
466
- await weflayr.propagateMetadata(
467
- {
468
- featureName: 'inner',
469
- customerName: 'inner_c',
470
- extraTags: { shared: 'inner', experiment: 'B' },
471
- },
472
- async () => {
473
- const span = tracer.startSpan('child');
474
- span.setAttribute('gen_ai.system', 'openai');
475
- span.end();
476
- }
477
- );
478
- }
479
- );
480
-
481
- await proc.forceFlush();
482
- const attrs = exporter.exported[0].attributes;
483
- assert.equal(attrs['weflayr.feature_name'], 'inner');
484
- assert.equal(attrs['weflayr.customer_name'], 'inner_c');
485
- // Outer extra tag not shadowed survives
486
- assert.equal(attrs['weflayr.tag.variant'], 'A');
487
- // Inner extra tag wins on conflict
488
- assert.equal(attrs['weflayr.tag.shared'], 'inner');
489
- assert.equal(attrs['weflayr.tag.experiment'], 'B');
490
- await provider.shutdown();
491
- });
492
-
493
- test('propagateMetadata decorator mints fresh uuid per invocation', async () => {
494
- const { exporter, proc, provider, tracer } = _makeMetadataHarness();
495
-
496
- const wrap = weflayr.propagateMetadata({ featureName: 'f', customerName: 'c' });
497
- const leaf = wrap(async function leaf() {
498
- const span = tracer.startSpan('child');
499
- span.setAttribute('gen_ai.system', 'openai');
500
- span.end();
501
- });
502
-
503
- await leaf();
504
- await leaf();
505
-
506
- await proc.forceFlush();
507
- const uuidFirst = exporter.exported[0].attributes['weflayr.uuid'];
508
- const uuidSecond = exporter.exported[1].attributes['weflayr.uuid'];
509
- assert.notEqual(uuidFirst, uuidSecond);
510
- await provider.shutdown();
511
- });
512
-
513
- test('propagateMetadata decorator form wraps a function', async () => {
514
- const { exporter, proc, provider, tracer } = _makeMetadataHarness();
515
-
516
- const wrap = weflayr.propagateMetadata({
517
- featureName: 'chat',
518
- customerName: 'c_42',
519
- extraTags: { experiment: 'B' },
520
- });
521
- const leaf = wrap(async function leaf() {
522
- const span = tracer.startSpan('child');
523
- span.setAttribute('gen_ai.system', 'openai');
524
- span.end();
525
- });
526
-
527
- await leaf();
528
- await proc.forceFlush();
529
- const attrs = exporter.exported[0].attributes;
530
- assert.equal(attrs['weflayr.feature_name'], 'chat');
531
- assert.equal(attrs['weflayr.customer_name'], 'c_42');
532
- assert.equal(attrs['weflayr.tag.experiment'], 'B');
533
- await provider.shutdown();
534
- });
535
-
536
- test('propagateMetadata callable form runs function', async () => {
537
- const { exporter, proc, provider, tracer } = _makeMetadataHarness();
538
-
539
- const result = await weflayr.propagateMetadata(
540
- {
541
- featureName: 'callable',
542
- customerName: 'c_42',
543
- extraTags: { variant: 'A' },
544
- },
545
- async () => {
546
- const span = tracer.startSpan('child');
547
- span.setAttribute('gen_ai.system', 'openai');
548
- span.end();
549
- return 'done';
550
- }
551
- );
552
-
553
- await proc.forceFlush();
554
- assert.equal(result, 'done');
555
- const attrs = exporter.exported[0].attributes;
556
- assert.equal(attrs['weflayr.feature_name'], 'callable');
557
- assert.equal(attrs['weflayr.tag.variant'], 'A');
558
- await provider.shutdown();
559
- });
560
-
561
- test('propagateMetadata providerName writes attribute', async () => {
562
- const { exporter, proc, provider, tracer } = _makeMetadataHarness();
563
-
564
- await weflayr.propagateMetadata(
565
- {
566
- featureName: 'f',
567
- customerName: 'c',
568
- providerName: weflayr.AIProviderName.VERCEL_AI_GATEWAY,
569
- },
570
- async () => {
571
- const span = tracer.startSpan('child');
572
- span.setAttribute('gen_ai.system', 'openai');
573
- span.end();
574
- }
575
- );
576
-
577
- await proc.forceFlush();
578
- assert.equal(exporter.exported[0].attributes['weflayr.provider_name'], 'vercel_ai_gateway');
579
- await provider.shutdown();
580
- });
581
-
582
- test('propagateMetadata requires featureName and customerName', () => {
583
- assert.throws(() => weflayr.propagateMetadata({}), /featureName.*customerName.*required/);
584
- assert.throws(() => weflayr.propagateMetadata({ featureName: 'f' }), /required/);
585
- assert.throws(() => weflayr.propagateMetadata({ customerName: 'c' }), /required/);
586
- });
587
-
588
- test('propagateMetadata deprecated customerId still works', async () => {
589
- const { exporter, proc, provider, tracer } = _makeMetadataHarness();
590
-
591
- const warnings = [];
592
- const onWarning = (warning) => warnings.push(warning);
593
- process.on('warning', onWarning);
594
-
595
- await weflayr.propagateMetadata({ featureName: 'f', customerId: 'Acme Corp' }, async () => {
596
- const span = tracer.startSpan('child');
597
- span.setAttribute('gen_ai.system', 'openai');
598
- span.end();
599
- });
600
-
601
- // process.emitWarning delivers on a later tick; drain it before asserting.
602
- await new Promise((resolve) => process.nextTick(resolve));
603
- process.removeListener('warning', onWarning);
604
- assert.ok(warnings.some((warning) => /customerId.*deprecated/.test(warning.message)));
605
-
606
- await proc.forceFlush();
607
- const attrs = exporter.exported[0].attributes;
608
- assert.equal(attrs['weflayr.customer_name'], 'Acme Corp');
609
- await provider.shutdown();
610
- });
611
-
612
- // ── autoInstrument ────────────────────────────────────────────────────────────
613
-
614
- function _ensureSetupDone() {
615
- if (!_state.processor) weflayr.setup({ apiKey: 'test-key' });
616
- }
617
-
618
- // Instrumenting OPENAI also runs the bundled audio + embeddings instrumentors,
619
- // which eagerly require the openai resource modules. Stub them so autoInstrument
620
- // tests don't need the real `openai` dependency installed.
621
- const OPENAI_RESOURCE_STUBS = {
622
- 'openai/resources/audio/speech': {
623
- Speech: class {
624
- create() {}
625
- },
626
- },
627
- 'openai/resources/audio/transcriptions': {
628
- Transcriptions: class {
629
- create() {}
630
- },
631
- },
632
- 'openai/resources/embeddings': {
633
- Embeddings: class {
634
- create() {}
635
- },
636
- },
637
- };
638
-
639
- function _stubOpenAIResources() {
640
- for (const [id, exports] of Object.entries(OPENAI_RESOURCE_STUBS)) {
641
- require.cache[id] = { id, filename: id, loaded: true, exports };
642
- }
643
- return () => {
644
- for (const id of Object.keys(OPENAI_RESOURCE_STUBS)) delete require.cache[id];
645
- };
646
- }
647
-
648
- test('autoInstrument throws "install package X" when the dep is missing', async () => {
649
- _ensureSetupDone();
650
- await assert.rejects(
651
- () => weflayr.autoInstrument({ aiSdks: weflayr.AiSdk.OPENAI }),
652
- /Install it with: npm install @traceloop\/instrumentation-openai/
653
- );
654
- });
655
-
656
- test('autoInstrument rejects unknown providers', async () => {
657
- // JS keeps this runtime check because `AiSdk` is a frozen object — it
658
- // doesn't enforce membership at the value level the way Python's `Enum` does.
659
- _ensureSetupDone();
660
- await assert.rejects(() => weflayr.autoInstrument({ aiSdks: 'unicorn-llm' }), /unknown provider/);
661
- });
662
-
663
- test('autoInstrument loads a stubbed Traceloop package', async () => {
664
- _ensureSetupDone();
665
- let constructed = false;
666
- class FakeOpenAIInstrumentation {
667
- constructor() {
668
- constructed = true;
669
- }
670
- enable() {}
671
- disable() {}
672
- setTracerProvider() {}
673
- setMeterProvider() {}
674
- setConfig() {}
675
- getConfig() {
676
- return {};
677
- }
678
- }
679
- const origResolve = Module._resolveFilename;
680
- Module._resolveFilename = function patched(req, ...rest) {
681
- if (req === '@traceloop/instrumentation-openai' || req in OPENAI_RESOURCE_STUBS) return req;
682
- return origResolve.call(this, req, ...rest);
683
- };
684
- require.cache['@traceloop/instrumentation-openai'] = {
685
- id: '@traceloop/instrumentation-openai',
686
- filename: '@traceloop/instrumentation-openai',
687
- loaded: true,
688
- exports: { OpenAIInstrumentation: FakeOpenAIInstrumentation },
689
- };
690
- const cleanupOpenAI = _stubOpenAIResources();
691
- try {
692
- await weflayr.autoInstrument({ aiSdks: weflayr.AiSdk.OPENAI });
693
- assert.equal(constructed, true);
694
- } finally {
695
- Module._resolveFilename = origResolve;
696
- delete require.cache['@traceloop/instrumentation-openai'];
697
- cleanupOpenAI();
698
- }
699
- });
700
-
701
- test('autoInstrument throws when no apiKey is reachable (env nor argument)', async () => {
702
- _state.processor = null;
703
- const saved = process.env.WEFLAYR_API_KEY;
704
- delete process.env.WEFLAYR_API_KEY;
705
- try {
706
- await assert.rejects(
707
- () => weflayr.autoInstrument({ aiSdks: weflayr.AiSdk.OPENAI }),
708
- /apiKey.*required/
709
- );
710
- } finally {
711
- if (saved !== undefined) process.env.WEFLAYR_API_KEY = saved;
712
- }
713
- });
714
-
715
- test('autoInstrument with an empty aiSdks list still runs setup', async () => {
716
- // The vercel instrumentor's ai <= 6 error message tells users to keep their
717
- // autoInstrument call with 'vercel_ai_gateway' removed (possibly leaving an
718
- // empty list) — that must still install the processor.
719
- _state.processor = null;
720
- await weflayr.autoInstrument({ aiSdks: [], apiKey: 'empty-list-key' });
721
- assert.ok(_state.processor, 'processor should be installed by the embedded setup() call');
722
- });
723
-
724
- test('autoInstrument runs setup when apiKey passed', async () => {
725
- _state.processor = null;
726
- class FakeOpenAIInstrumentation {
727
- constructor() {}
728
- enable() {}
729
- disable() {}
730
- setTracerProvider() {}
731
- setMeterProvider() {}
732
- setConfig() {}
733
- getConfig() {
734
- return {};
735
- }
736
- }
737
- const origResolve = Module._resolveFilename;
738
- Module._resolveFilename = function patched(req, ...rest) {
739
- if (req === '@traceloop/instrumentation-openai' || req in OPENAI_RESOURCE_STUBS) return req;
740
- return origResolve.call(this, req, ...rest);
741
- };
742
- require.cache['@traceloop/instrumentation-openai'] = {
743
- id: '@traceloop/instrumentation-openai',
744
- filename: '@traceloop/instrumentation-openai',
745
- loaded: true,
746
- exports: { OpenAIInstrumentation: FakeOpenAIInstrumentation },
747
- };
748
- const cleanupOpenAI = _stubOpenAIResources();
749
- try {
750
- await weflayr.autoInstrument({
751
- aiSdks: weflayr.AiSdk.OPENAI,
752
- apiKey: 'convenience-key',
753
- });
754
- assert.ok(_state.processor, 'processor should be installed by the embedded setup() call');
755
- } finally {
756
- Module._resolveFilename = origResolve;
757
- delete require.cache['@traceloop/instrumentation-openai'];
758
- cleanupOpenAI();
759
- }
760
- });
761
-
762
- test('autoInstrument requires aiSdks', async () => {
763
- _state.processor = null;
764
- await assert.rejects(() => weflayr.autoInstrument({ apiKey: 'k' }), /`aiSdks` is required/);
765
- });
766
-
767
- // ── exporter retry ────────────────────────────────────────────────────────────
768
-
769
- function _failResult(status) {
770
- return { code: 1, error: { code: status, message: `HTTP ${status}` } };
771
- }
772
-
773
- test('exporter retries on 503 then succeeds', async () => {
774
- const responses = [_failResult(503), _failResult(503), { code: 0 }];
775
- let i = 0;
776
- const fakeExport = (spans, cb) => cb(responses[i++]);
777
- const wrapped = _exportWithRetry(fakeExport, { maxRetries: 3, maxRetryMs: 5000 });
778
- const result = await new Promise((resolve) => wrapped([], resolve));
779
- assert.equal(result.code, 0);
780
- assert.equal(i, 3);
781
- });
782
-
783
- test('exporter does not retry on 401', async () => {
784
- let calls = 0;
785
- const fakeExport = (spans, cb) => {
786
- calls++;
787
- cb(_failResult(401));
788
- };
789
- const wrapped = _exportWithRetry(fakeExport, { maxRetries: 3, maxRetryMs: 5000 });
790
- const result = await new Promise((resolve) => wrapped([], resolve));
791
- assert.equal(result.code, 1);
792
- assert.equal(result.error.code, 401);
793
- assert.equal(calls, 1);
794
- });
795
-
796
- test('exporter swallows unexpected exceptions', async () => {
797
- const fakeExport = () => {
798
- throw new Error('boom');
799
- };
800
- const wrapped = _exportWithRetry(fakeExport, { maxRetries: 1, maxRetryMs: 5000 });
801
- const result = await new Promise((resolve) => wrapped([], resolve));
802
- assert.equal(result.code, 1);
803
- });
804
-
805
- test('exporter gives up after maxRetries on persistent 503', async () => {
806
- let calls = 0;
807
- const fakeExport = (spans, cb) => {
808
- calls++;
809
- cb(_failResult(503));
810
- };
811
- const wrapped = _exportWithRetry(fakeExport, { maxRetries: 3, maxRetryMs: 5000 });
812
- const result = await new Promise((resolve) => wrapped([], resolve));
813
- assert.equal(result.code, 1);
814
- assert.equal(result.error.code, 503);
815
- assert.equal(calls, 3);
816
- });
817
-
818
- // ── flush ─────────────────────────────────────────────────────────────────────
819
-
820
- test('flush does not throw', async () => {
821
- await weflayr.flush();
822
- });
823
-
824
- void trace;