dd-trace 6.0.0 → 6.2.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 (98) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/index.d.ts +28 -1
  3. package/initialize.mjs +4 -2
  4. package/package.json +34 -30
  5. package/packages/datadog-instrumentations/src/ai.js +45 -0
  6. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  7. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  8. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  9. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  10. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  11. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +5 -1
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +31 -0
  19. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +19 -5
  20. package/packages/datadog-instrumentations/src/jest.js +713 -63
  21. package/packages/datadog-instrumentations/src/mercurius.js +11 -0
  22. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  24. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  25. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  26. package/packages/datadog-instrumentations/src/mongoose.js +4 -5
  27. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +15 -0
  28. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  29. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
  30. package/packages/datadog-instrumentations/src/vitest-main.js +26 -1
  31. package/packages/datadog-instrumentations/src/vitest-util.js +2 -0
  32. package/packages/datadog-instrumentations/src/vitest-worker.js +103 -34
  33. package/packages/datadog-plugin-ai/src/index.js +1 -1
  34. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  35. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  36. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
  37. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  38. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  39. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  40. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  41. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  42. package/packages/datadog-plugin-cucumber/src/index.js +14 -23
  43. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
  44. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  45. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  46. package/packages/datadog-plugin-graphql/src/execute.js +25 -25
  47. package/packages/datadog-plugin-graphql/src/index.js +23 -1
  48. package/packages/datadog-plugin-graphql/src/request.js +104 -0
  49. package/packages/datadog-plugin-graphql/src/utils.js +186 -2
  50. package/packages/datadog-plugin-graphql/src/validate.js +25 -2
  51. package/packages/datadog-plugin-jest/src/index.js +30 -14
  52. package/packages/datadog-plugin-mocha/src/index.js +39 -14
  53. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  54. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  55. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  56. package/packages/datadog-plugin-playwright/src/index.js +3 -2
  57. package/packages/datadog-plugin-vitest/src/index.js +43 -9
  58. package/packages/dd-trace/src/aiguard/index.js +9 -14
  59. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  60. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  61. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  62. package/packages/dd-trace/src/appsec/graphql.js +9 -6
  63. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
  64. package/packages/dd-trace/src/appsec/lambda.js +8 -8
  65. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  66. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  67. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
  68. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
  69. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
  70. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  71. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  72. package/packages/dd-trace/src/config/supported-configurations.json +37 -0
  73. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  74. package/packages/dd-trace/src/datastreams/writer.js +14 -2
  75. package/packages/dd-trace/src/encode/0.4.js +23 -2
  76. package/packages/dd-trace/src/encode/0.5.js +12 -1
  77. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +11 -1
  78. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +20 -2
  79. package/packages/dd-trace/src/exporters/common/writer.js +17 -1
  80. package/packages/dd-trace/src/guardrails/index.js +3 -1
  81. package/packages/dd-trace/src/guardrails/telemetry.js +40 -3
  82. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  83. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  84. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  85. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  86. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  87. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  88. package/packages/dd-trace/src/msgpack/chunk.js +33 -1
  89. package/packages/dd-trace/src/msgpack/index.js +6 -1
  90. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  91. package/packages/dd-trace/src/plugin_manager.js +16 -0
  92. package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
  93. package/packages/dd-trace/src/plugins/index.js +5 -0
  94. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  95. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  96. package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +6 -0
  97. package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +8 -0
  98. package/vendor/dist/@apm-js-collab/code-transformer/index.js +5 -6
@@ -1,402 +1,15 @@
1
1
  'use strict'
2
2
 
3
- const { channel } = require('dc-polyfill')
4
- const BaseLLMObsPlugin = require('../base')
5
- const { getModelProvider } = require('../../../../../datadog-plugin-ai/src/utils')
3
+ const CompositePlugin = require('../../../plugins/composite')
4
+ const DdTelemetryPlugin = require('./ddTelemetry')
5
+ const VercelAiTelemetryPlugin = require('./vercelTelemetry')
6
6
 
7
- const toolCreationCh = channel('tracing:orchestrion:ai:tool:start')
8
- const setAttributesCh = channel('dd-trace:vercel-ai:span:setAttributes')
9
-
10
- const { MODEL_NAME, MODEL_PROVIDER, NAME } = require('../../constants/tags')
11
- const {
12
- getSpanTags,
13
- getOperation,
14
- getUsage,
15
- getJsonStringValue,
16
- getModelMetadata,
17
- getGenerationMetadata,
18
- getToolNameFromTags,
19
- getToolCallResultContent,
20
- getLlmObsSpanName,
21
- getTelemetryMetadata,
22
- } = require('./util')
23
-
24
- /**
25
- * @typedef {Record<string, unknown> & { description?: string, id?: string }} AvailableToolArgs
26
- */
27
-
28
- /**
29
- * @typedef {string | number | boolean | null | undefined | string[] | number[] | boolean[]} TagValue
30
- * @typedef {Record<string, TagValue>} SpanTags
31
- *
32
- * @typedef {{ name?: string, description?: string }} ToolForModel
33
- *
34
- * @typedef {{ type: 'text' | 'reasoning' | 'redacted-reasoning', text?: string, data?: string }} TextPart
35
- * @typedef {{ type: 'tool-call', toolName: string, toolCallId: string, args?: unknown, input?: unknown }} ToolCallPart
36
- * @typedef {(
37
- * { type: 'tool-result', toolCallId: string, output?: { type: string, value?: unknown }, result?: unknown } &
38
- * Record<string, unknown>
39
- * )} ToolResultPart
40
- *
41
- * @typedef {{
42
- * role: 'system',
43
- * content: string
44
- * } | {
45
- * role: 'user',
46
- * content: TextPart[]
47
- * } | {
48
- * role: 'assistant',
49
- * content: Array<TextPart | ToolCallPart>
50
- * } | {
51
- * role: 'tool',
52
- * content: ToolResultPart[]
53
- * }} AiSdkMessage
54
- */
55
-
56
- const SPAN_NAME_TO_KIND_MAPPING = {
57
- // embeddings
58
- embed: 'workflow',
59
- embedMany: 'workflow',
60
- doEmbed: 'embedding',
61
- // object generation
62
- generateObject: 'workflow',
63
- streamObject: 'workflow',
64
- // text generation
65
- generateText: 'workflow',
66
- streamText: 'workflow',
67
- // llm operations
68
- doGenerate: 'llm',
69
- doStream: 'llm',
70
- // tools
71
- toolCall: 'tool',
72
- }
73
-
74
- class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
75
- static id = 'ai'
7
+ class VercelAILLMObsPlugin extends CompositePlugin {
8
+ static id = 'ai_llmobs'
76
9
  static integration = 'ai'
77
- static prefix = 'tracing:dd-trace:vercel-ai'
78
-
79
- /**
80
- * The available tools within the runtime scope of this integration.
81
- * This essentially acts as a global registry for all tools made through the Vercel AI SDK.
82
- * @type {Set<AvailableToolArgs>}
83
- */
84
- #availableTools
85
-
86
- /**
87
- * A mapping of tool call IDs to tool names.
88
- * This is used to map the tool call ID to the tool name for the output message.
89
- * @type {Record<string, string>}
90
- */
91
- #toolCallIdsToName
92
-
93
- constructor (...args) {
94
- super(...args)
95
-
96
- this.#toolCallIdsToName = {}
97
- this.#availableTools = new Set()
98
- toolCreationCh.subscribe(ctx => {
99
- const toolArgs = ctx.arguments
100
- const tool = toolArgs[0] ?? {}
101
- this.#availableTools.add(tool)
102
- })
103
-
104
- setAttributesCh.subscribe(({ ctx, attributes }) => {
105
- Object.assign(ctx.attributes, attributes)
106
- })
107
- }
108
-
109
- /**
110
- * Does a best-effort attempt to find the right tool name for the given tool description.
111
- * This is because the Vercel AI SDK does not tag tools by name properly, but
112
- * rather by the index they were passed in. Tool names appear nowhere in the span tags.
113
- *
114
- * We use the tool description as the next best identifier for a tool.
115
- *
116
- * @param {string} toolName
117
- * @param {string | undefined} toolDescription
118
- * @returns {string | undefined}
119
- */
120
- findToolName (toolName, toolDescription) {
121
- if (Number.isNaN(Number.parseInt(toolName))) return toolName
122
-
123
- for (const availableTool of this.#availableTools) {
124
- const description = availableTool.description
125
- if (description === toolDescription && availableTool.id) {
126
- return availableTool.id
127
- }
128
- }
129
- }
130
-
131
- /**
132
- * @override
133
- */
134
- getLLMObsSpanRegisterOptions (ctx) {
135
- const span = ctx.currentStore?.span
136
- const operation = getOperation(span)
137
- const kind = SPAN_NAME_TO_KIND_MAPPING[operation]
138
- if (!kind) return
139
-
140
- return { kind, name: getLlmObsSpanName(operation, ctx.attributes['ai.telemetry.functionId']) }
141
- }
142
-
143
- /**
144
- * @override
145
- */
146
- setLLMObsTags (ctx) {
147
- const span = ctx.currentStore?.span
148
- if (!span) return
149
-
150
- const operation = getOperation(span)
151
- const kind = SPAN_NAME_TO_KIND_MAPPING[operation]
152
- if (!kind) return
153
-
154
- const tags = getSpanTags(ctx)
155
-
156
- if (['embedding', 'llm'].includes(kind)) {
157
- this._tagger._setTag(span, MODEL_NAME, tags['ai.model.id'])
158
- this._tagger._setTag(span, MODEL_PROVIDER, getModelProvider(tags))
159
- }
160
-
161
- switch (operation) {
162
- case 'embed':
163
- case 'embedMany':
164
- this.setEmbeddingWorkflowTags(span, tags)
165
- break
166
- case 'doEmbed':
167
- this.setEmbeddingTags(span, tags)
168
- break
169
- case 'generateObject':
170
- case 'streamObject':
171
- this.setObjectGenerationTags(span, tags)
172
- break
173
- case 'generateText':
174
- case 'streamText':
175
- this.setTextGenerationTags(span, tags)
176
- break
177
- case 'doGenerate':
178
- case 'doStream':
179
- this.setLLMOperationTags(span, tags)
180
- break
181
- case 'toolCall':
182
- this.setToolTags(span, tags)
183
- break
184
- default:
185
- break
186
- }
187
- }
188
-
189
- setEmbeddingWorkflowTags (span, tags) {
190
- const inputs = tags['ai.value'] ?? tags['ai.values']
191
- const parsedInputs = Array.isArray(inputs)
192
- ? inputs.map(input => getJsonStringValue(input, ''))
193
- : getJsonStringValue(inputs, '')
194
-
195
- const embeddingsOutput = tags['ai.embedding'] ?? tags['ai.embeddings']
196
- const isSingleEmbedding = !Array.isArray(embeddingsOutput)
197
- const numberOfEmbeddings = isSingleEmbedding ? 1 : embeddingsOutput.length
198
- const embeddingsLength = getJsonStringValue(isSingleEmbedding ? embeddingsOutput : embeddingsOutput?.[0], []).length
199
- const output = `[${numberOfEmbeddings} embedding(s) returned with size ${embeddingsLength}]`
200
-
201
- this._tagger.tagTextIO(span, parsedInputs, output)
202
-
203
- const metadata = getGenerationMetadata(tags)
204
- this._tagger.tagMetadata(span, metadata)
205
- }
206
-
207
- setEmbeddingTags (span, tags) {
208
- const inputs = tags['ai.values']
209
- if (!Array.isArray(inputs)) return
210
-
211
- const parsedInputs = inputs.map(input => getJsonStringValue(input, ''))
212
-
213
- const embeddingsOutput = tags['ai.embeddings']
214
- const numberOfEmbeddings = embeddingsOutput?.length
215
- const embeddingsLength = getJsonStringValue(embeddingsOutput?.[0], []).length
216
- const output = `[${numberOfEmbeddings} embedding(s) returned with size ${embeddingsLength}]`
217
-
218
- this._tagger.tagEmbeddingIO(span, parsedInputs, output)
219
-
220
- const metadata = getTelemetryMetadata(tags)
221
- this._tagger.tagMetadata(span, metadata)
222
-
223
- const usage = tags['ai.usage.tokens']
224
- this._tagger.tagMetrics(span, {
225
- inputTokens: usage,
226
- totalTokens: usage,
227
- })
228
- }
229
-
230
- setObjectGenerationTags (span, tags) {
231
- const promptInfo = getJsonStringValue(tags['ai.prompt'], {})
232
- const lastUserPrompt =
233
- promptInfo.prompt ??
234
- promptInfo.messages.reverse().find(message => message.role === 'user')?.content
235
- const prompt = Array.isArray(lastUserPrompt) ? lastUserPrompt.map(part => part.text ?? '').join('') : lastUserPrompt
236
-
237
- const output = tags['ai.response.object']
238
-
239
- this._tagger.tagTextIO(span, prompt, output)
240
-
241
- const metadata = getGenerationMetadata(tags)
242
- metadata.schema = getJsonStringValue(tags['ai.schema'], {})
243
- this._tagger.tagMetadata(span, metadata)
244
- }
245
-
246
- setTextGenerationTags (span, tags) {
247
- const promptInfo = getJsonStringValue(tags['ai.prompt'], {})
248
- const lastUserPrompt =
249
- promptInfo.prompt ??
250
- promptInfo.messages.reverse().find(message => message.role === 'user')?.content
251
- const prompt = Array.isArray(lastUserPrompt) ? lastUserPrompt.map(part => part.text ?? '').join('') : lastUserPrompt
252
-
253
- const output = tags['ai.response.text']
254
-
255
- this._tagger.tagTextIO(span, prompt, output)
256
-
257
- const metadata = getGenerationMetadata(tags)
258
- this._tagger.tagMetadata(span, metadata)
259
- }
260
-
261
- /**
262
- * @param {import('../../../opentracing/span')} span
263
- * @param {SpanTags} tags
264
- */
265
- setLLMOperationTags (span, tags) {
266
- const toolsForModel = tags['ai.prompt.tools']?.map(getJsonStringValue)
267
-
268
- const inputMessages = getJsonStringValue(tags['ai.prompt.messages'], [])
269
- const parsedInputMessages = []
270
- for (const message of inputMessages) {
271
- const formattedMessages = this.formatMessage(message, toolsForModel)
272
- parsedInputMessages.push(...formattedMessages)
273
- }
274
-
275
- const outputMessage = this.formatOutputMessage(tags, toolsForModel)
276
-
277
- this._tagger.tagLLMIO(span, parsedInputMessages, outputMessage)
278
-
279
- const metadata = getModelMetadata(tags)
280
- this._tagger.tagMetadata(span, metadata)
281
-
282
- const usage = getUsage(tags)
283
- this._tagger.tagMetrics(span, usage)
284
- }
285
-
286
- setToolTags (span, tags) {
287
- const toolCallId = tags['ai.toolCall.id']
288
- const name = getToolNameFromTags(tags) ?? this.#toolCallIdsToName[toolCallId]
289
- if (name) this._tagger._setTag(span, NAME, name)
290
-
291
- const input = tags['ai.toolCall.args']
292
- const output = tags['ai.toolCall.result']
293
-
294
- this._tagger.tagTextIO(span, input, output)
295
- }
296
-
297
- formatOutputMessage (tags, toolsForModel) {
298
- const outputMessageText = tags['ai.response.text'] ?? tags['ai.response.object']
299
- const outputMessageToolCalls = getJsonStringValue(tags['ai.response.toolCalls'], [])
300
-
301
- const formattedToolCalls = []
302
- for (const toolCall of outputMessageToolCalls) {
303
- const toolArgs = toolCall.args ?? toolCall.input
304
- const toolCallArgs = typeof toolArgs === 'string' ? getJsonStringValue(toolArgs, {}) : toolArgs
305
- const toolDescription = toolsForModel?.find(tool => toolCall.toolName === tool.name)?.description
306
- const name = this.findToolName(toolCall.toolName, toolDescription)
307
- this.#toolCallIdsToName[toolCall.toolCallId] = name
308
-
309
- formattedToolCalls.push({
310
- arguments: toolCallArgs,
311
- name,
312
- toolId: toolCall.toolCallId,
313
- type: toolCall.toolCallType ?? 'function',
314
- })
315
- }
316
-
317
- return {
318
- role: 'assistant',
319
- content: outputMessageText,
320
- toolCalls: formattedToolCalls,
321
- }
322
- }
323
-
324
- /**
325
- * Returns a list of formatted messages from a message object.
326
- * Most of these will just be one entry, but in the case of a "tool" role,
327
- * it is possible to have multiple tool call results in a single message that we
328
- * need to split into multiple messages.
329
- *
330
- * @param {AiSdkMessage} message
331
- * @param {ToolForModel[] | null | undefined} toolsForModel
332
- * @returns {Array<{role: string, content: string, toolId?: string,
333
- * toolCalls?: Array<{arguments: string, name: string, toolId: string, type: string}>}>}
334
- */
335
- formatMessage (message, toolsForModel) {
336
- const { role, content } = message
337
-
338
- if (role === 'system') {
339
- return [{ role, content }]
340
- } else if (role === 'user') {
341
- let finalContent = ''
342
- for (const part of content) {
343
- const { type } = part
344
- if (type === 'text') {
345
- finalContent += part.text
346
- }
347
- }
348
-
349
- return [{ role, content: finalContent }]
350
- } else if (role === 'assistant') {
351
- const toolCalls = []
352
- let finalContent = ''
353
-
354
- for (const part of content) {
355
- const { type } = part
356
- // TODO(sabrenner): do we want to include reasoning?
357
- if (['text', 'reasoning', 'redacted-reasoning'].includes(type)) {
358
- finalContent += part.text ?? part.data
359
- } else if (type === 'tool-call') {
360
- const toolDescription = toolsForModel?.find(tool => part.toolName === tool.name)?.description
361
- const name = this.findToolName(part.toolName, toolDescription)
362
-
363
- toolCalls.push({
364
- arguments: part.args ?? part.input,
365
- name,
366
- toolId: part.toolCallId,
367
- type: 'function',
368
- })
369
- }
370
- }
371
-
372
- const finalMessage = {
373
- role,
374
- content: finalContent,
375
- }
376
-
377
- if (toolCalls.length) {
378
- finalMessage.toolCalls = toolCalls.length ? toolCalls : undefined
379
- }
380
-
381
- return [finalMessage]
382
- } else if (role === 'tool') {
383
- const finalMessages = []
384
- for (const part of content) {
385
- if (part.type === 'tool-result') {
386
- const safeResult = getToolCallResultContent(part)
387
-
388
- finalMessages.push({
389
- role,
390
- content: safeResult,
391
- toolId: part.toolCallId,
392
- })
393
- }
394
- }
395
-
396
- return finalMessages
397
- }
398
-
399
- return []
10
+ static plugins = {
11
+ dd: DdTelemetryPlugin,
12
+ ai: VercelAiTelemetryPlugin,
400
13
  }
401
14
  }
402
15
 
@@ -259,6 +259,32 @@ function getGenerationMetadata (tags) {
259
259
  return Object.keys(metadata).length ? metadata : null
260
260
  }
261
261
 
262
+ /**
263
+ * Get the generation metadata from the span tags (maxSteps, maxRetries, etc.)
264
+ * Additionally, set telemetry metadata from manual telemetry tags.
265
+ * @param {Record<string, unknown>} event
266
+ * @returns {Record<string, unknown> | null}
267
+ */
268
+ function getGenerationMetadataFromEvent (event) {
269
+ /** @type {Record<string, unknown>} */
270
+ const metadata = {}
271
+
272
+ for (const [key, value] of Object.entries(event)) {
273
+ const transformedKey = key.replaceAll(/[A-Z]/g, letter => '_' + letter.toLowerCase())
274
+ if (!MODEL_METADATA_KEYS.has(transformedKey)) {
275
+ if (key === 'runtimeContext') { // custom telemetry metadata
276
+ Object.assign(metadata, value)
277
+ }
278
+
279
+ continue
280
+ }
281
+
282
+ metadata[transformedKey] = value
283
+ }
284
+
285
+ return Object.keys(metadata).length ? metadata : null
286
+ }
287
+
262
288
  /**
263
289
  * Get the tool name from the span tags.
264
290
  * If the tool name is a parsable number, or is not found, null is returned.
@@ -348,4 +374,5 @@ module.exports = {
348
374
  getToolCallResultContent,
349
375
  getLlmObsSpanName,
350
376
  getTelemetryMetadata,
377
+ getGenerationMetadataFromEvent,
351
378
  }