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