dd-trace 5.80.0 → 5.81.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +79 -88
  2. package/ext/tags.d.ts +1 -0
  3. package/ext/tags.js +1 -0
  4. package/index.d.ts +35 -35
  5. package/loader-hook.mjs +10 -3
  6. package/package.json +22 -40
  7. package/packages/datadog-esbuild/index.js +36 -19
  8. package/packages/datadog-instrumentations/index.js +1 -0
  9. package/packages/datadog-instrumentations/src/anthropic.js +12 -0
  10. package/packages/datadog-instrumentations/src/aws-sdk.js +5 -1
  11. package/packages/datadog-instrumentations/src/cucumber.js +2 -2
  12. package/packages/datadog-instrumentations/src/find-my-way.js +6 -5
  13. package/packages/datadog-instrumentations/src/google-genai.js +120 -0
  14. package/packages/datadog-instrumentations/src/graphql.js +20 -0
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +10 -0
  17. package/packages/datadog-instrumentations/src/helpers/register.js +6 -1
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +27 -0
  19. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +152 -0
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +5 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langchain.js +237 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.js +9 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +11 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +139 -0
  25. package/packages/datadog-instrumentations/src/langchain.js +3 -109
  26. package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
  27. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  28. package/packages/datadog-instrumentations/src/playwright.js +45 -16
  29. package/packages/datadog-instrumentations/src/router.js +1 -1
  30. package/packages/datadog-instrumentations/src/selenium.js +3 -1
  31. package/packages/datadog-instrumentations/src/ws.js +35 -17
  32. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
  33. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +23 -2
  34. package/packages/datadog-plugin-cypress/src/plugin.js +1 -1
  35. package/packages/datadog-plugin-cypress/src/support.js +73 -31
  36. package/packages/datadog-plugin-google-genai/src/index.js +17 -0
  37. package/packages/datadog-plugin-google-genai/src/tracing.js +41 -0
  38. package/packages/datadog-plugin-graphql/src/tools/transforms.js +5 -4
  39. package/packages/datadog-plugin-jest/src/util.js +1 -1
  40. package/packages/datadog-plugin-langchain/src/tracing.js +7 -3
  41. package/packages/datadog-plugin-next/src/index.js +11 -3
  42. package/packages/dd-trace/src/aiguard/sdk.js +18 -10
  43. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  44. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  45. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -1
  46. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -2
  47. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
  48. package/packages/dd-trace/src/appsec/reporter.js +0 -4
  49. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +4 -8
  50. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +4 -2
  51. package/packages/dd-trace/src/config.js +81 -7
  52. package/packages/dd-trace/src/config_defaults.js +14 -2
  53. package/packages/dd-trace/src/datastreams/encoding.js +23 -6
  54. package/packages/dd-trace/src/datastreams/pathway.js +40 -1
  55. package/packages/dd-trace/src/datastreams/processor.js +1 -1
  56. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -1
  57. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +15 -5
  58. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  59. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
  60. package/packages/dd-trace/src/debugger/devtools_client/index.js +30 -15
  61. package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +2 -0
  62. package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +24 -18
  63. package/packages/dd-trace/src/debugger/devtools_client/send.js +18 -8
  64. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +103 -15
  65. package/packages/dd-trace/src/debugger/devtools_client/snapshot/constants.js +25 -0
  66. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +56 -25
  67. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +64 -23
  68. package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +3 -1
  69. package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +404 -0
  70. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +1 -1
  71. package/packages/dd-trace/src/debugger/devtools_client/state.js +7 -2
  72. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  73. package/packages/dd-trace/src/debugger/index.js +1 -1
  74. package/packages/dd-trace/src/encode/span-stats.js +7 -1
  75. package/packages/dd-trace/src/histogram.js +1 -1
  76. package/packages/dd-trace/src/id.js +60 -0
  77. package/packages/dd-trace/src/lambda/runtime/ritm.js +1 -1
  78. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  79. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +104 -0
  80. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +486 -0
  81. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +2 -2
  82. package/packages/dd-trace/src/llmobs/plugins/{openai.js → openai/index.js} +48 -6
  83. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +114 -0
  84. package/packages/dd-trace/src/llmobs/sdk.js +5 -0
  85. package/packages/dd-trace/src/llmobs/span_processor.js +6 -1
  86. package/packages/dd-trace/src/llmobs/tagger.js +4 -0
  87. package/packages/dd-trace/src/opentelemetry/logs/index.js +2 -2
  88. package/packages/dd-trace/src/opentelemetry/logs/logger.js +3 -2
  89. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +5 -3
  90. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +8 -8
  91. package/packages/dd-trace/src/opentelemetry/metrics/constants.js +34 -0
  92. package/packages/dd-trace/src/opentelemetry/metrics/index.js +81 -0
  93. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +225 -0
  94. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +171 -0
  95. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +54 -0
  96. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +62 -0
  97. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +251 -0
  98. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +532 -0
  99. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +10 -18
  100. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +36 -22
  101. package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +1 -1
  102. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  103. package/packages/dd-trace/src/opentelemetry/tracer.js +1 -1
  104. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  105. package/packages/dd-trace/src/payload-tagging/index.js +2 -2
  106. package/packages/dd-trace/src/plugin_manager.js +4 -2
  107. package/packages/dd-trace/src/plugins/index.js +1 -0
  108. package/packages/dd-trace/src/plugins/util/test.js +3 -3
  109. package/packages/dd-trace/src/plugins/util/url.js +119 -1
  110. package/packages/dd-trace/src/plugins/util/web.js +10 -41
  111. package/packages/dd-trace/src/process-tags/index.js +81 -0
  112. package/packages/dd-trace/src/profiling/config.js +1 -1
  113. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  114. package/packages/dd-trace/src/profiling/profilers/events.js +10 -1
  115. package/packages/dd-trace/src/proxy.js +5 -0
  116. package/packages/dd-trace/src/rate_limiter.js +1 -1
  117. package/packages/dd-trace/src/remote_config/manager.js +1 -1
  118. package/packages/dd-trace/src/ritm.js +1 -1
  119. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
  120. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
  121. package/packages/dd-trace/src/span_format.js +9 -4
  122. package/packages/dd-trace/src/span_processor.js +8 -3
  123. package/packages/dd-trace/src/span_stats.js +15 -4
  124. package/packages/dd-trace/src/spanleak.js +1 -1
  125. package/packages/dd-trace/src/supported-configurations.json +13 -0
  126. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  127. package/packages/dd-trace/src/telemetry/telemetry.js +11 -2
  128. package/vendor/dist/@datadog/sketches-js/LICENSE +39 -0
  129. package/vendor/dist/@datadog/sketches-js/index.js +1 -0
  130. package/vendor/dist/@datadog/source-map/LICENSE +28 -0
  131. package/vendor/dist/@datadog/source-map/index.js +1 -0
  132. package/vendor/dist/@isaacs/ttlcache/LICENSE +55 -0
  133. package/vendor/dist/@isaacs/ttlcache/index.js +1 -0
  134. package/vendor/dist/@opentelemetry/core/LICENSE +201 -0
  135. package/vendor/dist/@opentelemetry/core/index.js +1 -0
  136. package/vendor/dist/@opentelemetry/resources/LICENSE +201 -0
  137. package/vendor/dist/@opentelemetry/resources/index.js +1 -0
  138. package/vendor/dist/astring/LICENSE +19 -0
  139. package/vendor/dist/astring/index.js +1 -0
  140. package/vendor/dist/crypto-randomuuid/index.js +1 -0
  141. package/vendor/dist/escape-string-regexp/LICENSE +9 -0
  142. package/vendor/dist/escape-string-regexp/index.js +1 -0
  143. package/vendor/dist/esquery/LICENSE +24 -0
  144. package/vendor/dist/esquery/index.js +1 -0
  145. package/vendor/dist/ignore/LICENSE +21 -0
  146. package/vendor/dist/ignore/index.js +1 -0
  147. package/vendor/dist/istanbul-lib-coverage/LICENSE +24 -0
  148. package/vendor/dist/istanbul-lib-coverage/index.js +1 -0
  149. package/vendor/dist/jest-docblock/LICENSE +21 -0
  150. package/vendor/dist/jest-docblock/index.js +1 -0
  151. package/vendor/dist/jsonpath-plus/LICENSE +22 -0
  152. package/vendor/dist/jsonpath-plus/index.js +1 -0
  153. package/vendor/dist/limiter/LICENSE +19 -0
  154. package/vendor/dist/limiter/index.js +1 -0
  155. package/vendor/dist/lodash.sortby/LICENSE +47 -0
  156. package/vendor/dist/lodash.sortby/index.js +1 -0
  157. package/vendor/dist/lru-cache/LICENSE +15 -0
  158. package/vendor/dist/lru-cache/index.js +1 -0
  159. package/vendor/dist/meriyah/LICENSE +7 -0
  160. package/vendor/dist/meriyah/index.js +1 -0
  161. package/vendor/dist/module-details-from-path/LICENSE +21 -0
  162. package/vendor/dist/module-details-from-path/index.js +1 -0
  163. package/vendor/dist/mutexify/promise/LICENSE +21 -0
  164. package/vendor/dist/mutexify/promise/index.js +1 -0
  165. package/vendor/dist/opentracing/LICENSE +201 -0
  166. package/vendor/dist/opentracing/binary_carrier.d.ts +11 -0
  167. package/vendor/dist/opentracing/constants.d.ts +61 -0
  168. package/vendor/dist/opentracing/examples/demo/demo.d.ts +2 -0
  169. package/vendor/dist/opentracing/ext/tags.d.ts +90 -0
  170. package/vendor/dist/opentracing/functions.d.ts +20 -0
  171. package/vendor/dist/opentracing/global_tracer.d.ts +14 -0
  172. package/vendor/dist/opentracing/index.d.ts +12 -0
  173. package/vendor/dist/opentracing/index.js +1 -0
  174. package/vendor/dist/opentracing/mock_tracer/index.d.ts +5 -0
  175. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +13 -0
  176. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +16 -0
  177. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +50 -0
  178. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +26 -0
  179. package/vendor/dist/opentracing/noop.d.ts +8 -0
  180. package/vendor/dist/opentracing/reference.d.ts +33 -0
  181. package/vendor/dist/opentracing/span.d.ts +147 -0
  182. package/vendor/dist/opentracing/span_context.d.ts +26 -0
  183. package/vendor/dist/opentracing/test/api_compatibility.d.ts +16 -0
  184. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +3 -0
  185. package/vendor/dist/opentracing/test/noop_implementation.d.ts +4 -0
  186. package/vendor/dist/opentracing/test/opentracing_api.d.ts +3 -0
  187. package/vendor/dist/opentracing/test/unittest.d.ts +2 -0
  188. package/vendor/dist/opentracing/tracer.d.ts +127 -0
  189. package/vendor/dist/path-to-regexp/LICENSE +21 -0
  190. package/vendor/dist/path-to-regexp/index.js +1 -0
  191. package/vendor/dist/pprof-format/LICENSE +8 -0
  192. package/vendor/dist/pprof-format/index.js +1 -0
  193. package/vendor/dist/protobufjs/LICENSE +39 -0
  194. package/vendor/dist/protobufjs/index.js +1 -0
  195. package/vendor/dist/protobufjs/minimal/LICENSE +39 -0
  196. package/vendor/dist/protobufjs/minimal/index.js +1 -0
  197. package/vendor/dist/retry/LICENSE +21 -0
  198. package/vendor/dist/retry/index.js +1 -0
  199. package/vendor/dist/rfdc/LICENSE +15 -0
  200. package/vendor/dist/rfdc/index.js +1 -0
  201. package/vendor/dist/semifies/LICENSE +201 -0
  202. package/vendor/dist/semifies/index.js +1 -0
  203. package/vendor/dist/shell-quote/LICENSE +24 -0
  204. package/vendor/dist/shell-quote/index.js +1 -0
  205. package/vendor/dist/source-map/LICENSE +28 -0
  206. package/vendor/dist/source-map/index.js +1 -0
  207. package/vendor/dist/source-map/lib/util/LICENSE +28 -0
  208. package/vendor/dist/source-map/lib/util/index.js +1 -0
  209. package/vendor/dist/source-map/mappings.wasm +0 -0
  210. package/vendor/dist/tlhunter-sorted-set/LICENSE +21 -0
  211. package/vendor/dist/tlhunter-sorted-set/index.js +1 -0
  212. package/vendor/dist/ttl-set/LICENSE +21 -0
  213. package/vendor/dist/ttl-set/index.js +1 -0
@@ -0,0 +1,104 @@
1
+ 'use strict'
2
+
3
+ const LLMObsPlugin = require('../base')
4
+ const {
5
+ getOperation,
6
+ extractMetrics,
7
+ extractMetadata,
8
+ aggregateStreamingChunks,
9
+ formatInputMessages,
10
+ formatEmbeddingInput,
11
+ formatOutputMessages,
12
+ formatEmbeddingOutput
13
+ } = require('./util')
14
+
15
+ class GenAiLLMObsPlugin extends LLMObsPlugin {
16
+ static id = 'google-genai'
17
+ static integration = 'google_genai'
18
+ static prefix = 'tracing:apm:google:genai:request'
19
+
20
+ constructor () {
21
+ super(...arguments)
22
+
23
+ // Subscribe to streaming chunk events
24
+ this.addSub('apm:google:genai:request:chunk', ({ ctx, chunk, done }) => {
25
+ ctx.isStreaming = true
26
+ ctx.chunks = ctx.chunks || []
27
+
28
+ if (chunk) ctx.chunks.push(chunk)
29
+ if (!done) return
30
+
31
+ // Aggregate streaming chunks into a single response
32
+ ctx.result = aggregateStreamingChunks(ctx.chunks)
33
+ })
34
+ }
35
+
36
+ getLLMObsSpanRegisterOptions (ctx) {
37
+ const { args, methodName } = ctx
38
+ if (!methodName) return
39
+
40
+ const inputs = args[0]
41
+ const operation = getOperation(methodName)
42
+
43
+ return {
44
+ modelProvider: 'google',
45
+ modelName: inputs.model,
46
+ kind: operation,
47
+ name: 'google_genai.request'
48
+ }
49
+ }
50
+
51
+ setLLMObsTags (ctx) {
52
+ const { args, methodName } = ctx
53
+ const span = ctx.currentStore?.span
54
+ if (!methodName) return
55
+
56
+ const inputs = args[0]
57
+ const response = ctx.result
58
+ const error = !!span.context()._tags.error
59
+
60
+ const operation = getOperation(methodName)
61
+
62
+ if (operation === 'llm') {
63
+ this.#tagGenerateContent(span, inputs, response, error, ctx.isStreaming)
64
+ } else if (operation === 'embedding') {
65
+ this.#tagEmbedding(span, inputs, response, error)
66
+ }
67
+
68
+ if (!error && response) {
69
+ const metrics = extractMetrics(response)
70
+ this._tagger.tagMetrics(span, metrics)
71
+ }
72
+ }
73
+
74
+ #tagGenerateContent (span, inputs, response, error, isStreaming = false) {
75
+ const { config = {} } = inputs
76
+
77
+ const inputMessages = formatInputMessages(inputs.contents)
78
+
79
+ const metadata = extractMetadata(config)
80
+ this._tagger.tagMetadata(span, metadata)
81
+
82
+ if (error) {
83
+ this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
84
+ return
85
+ }
86
+
87
+ const outputMessages = formatOutputMessages(response, isStreaming)
88
+ this._tagger.tagLLMIO(span, inputMessages, outputMessages)
89
+ }
90
+
91
+ #tagEmbedding (span, inputs, response, error) {
92
+ const embeddingInput = formatEmbeddingInput(inputs.contents)
93
+
94
+ if (error) {
95
+ this._tagger.tagEmbeddingIO(span, embeddingInput)
96
+ return
97
+ }
98
+
99
+ const embeddingOutput = formatEmbeddingOutput(response)
100
+ this._tagger.tagEmbeddingIO(span, embeddingInput, embeddingOutput)
101
+ }
102
+ }
103
+
104
+ module.exports = GenAiLLMObsPlugin
@@ -0,0 +1,486 @@
1
+ 'use strict'
2
+
3
+ // Constants for role mapping
4
+ const ROLES = {
5
+ MODEL: 'model',
6
+ ASSISTANT: 'assistant',
7
+ USER: 'user',
8
+ REASONING: 'reasoning'
9
+ }
10
+
11
+ /**
12
+ * Get the operation type from the method name
13
+ * @param {string} methodName
14
+ * @returns {'embedding' | 'llm'}
15
+ */
16
+ function getOperation (methodName) {
17
+ return methodName.includes('embed') ? 'embedding' : 'llm'
18
+ }
19
+
20
+ /**
21
+ * Extract text parts from an array of parts
22
+ * @param {Array<{text?: string}>} parts
23
+ * @returns {string[]}
24
+ */
25
+ function extractTextParts (parts) {
26
+ return parts
27
+ .filter(part => part.text)
28
+ .map(part => part.text)
29
+ }
30
+
31
+ /**
32
+ * Group parts by role (reasoning vs assistant)
33
+ * @param {Array<{text?: string, thought?: boolean}>} parts
34
+ * @returns {{reasoning: string, assistant: string}}
35
+ */
36
+ function groupPartsByRole (parts) {
37
+ const grouped = {
38
+ reasoning: '',
39
+ assistant: ''
40
+ }
41
+
42
+ for (const part of parts) {
43
+ if (!part.text) continue
44
+
45
+ if (part.thought === true) {
46
+ grouped.reasoning += part.text
47
+ } else {
48
+ grouped.assistant += part.text
49
+ }
50
+ }
51
+
52
+ return grouped
53
+ }
54
+
55
+ /**
56
+ * Check if parts contain thought/reasoning content
57
+ * @param {Array<{thought?: boolean}>} parts
58
+ * @returns {boolean}
59
+ */
60
+ function hasThoughtParts (parts) {
61
+ return parts.some(part => part.thought === true)
62
+ }
63
+
64
+ /**
65
+ * Determine the role from a candidate and its parts
66
+ * @param {object} candidate
67
+ * @param {Array<{thought?: boolean}>} parts
68
+ * @returns {string}
69
+ */
70
+ function determineRole (candidate, parts = []) {
71
+ // Check parts for thought indicators
72
+ if (hasThoughtParts(parts)) {
73
+ return ROLES.REASONING
74
+ }
75
+
76
+ // Extract role from various possible locations
77
+ const rawRole = candidate.role ||
78
+ candidate.content?.role ||
79
+ candidate[0]?.content?.role
80
+
81
+ return normalizeRole(rawRole)
82
+ }
83
+
84
+ /**
85
+ * Normalize role to standard values
86
+ * @param {string} role
87
+ * @returns {string}
88
+ */
89
+ function normalizeRole (role) {
90
+ if (role === ROLES.MODEL) return ROLES.ASSISTANT
91
+ if (role === ROLES.ASSISTANT) return ROLES.ASSISTANT
92
+ if (role === ROLES.USER) return ROLES.USER
93
+ if (role === ROLES.REASONING) return ROLES.REASONING
94
+ return ROLES.USER // default
95
+ }
96
+
97
+ /**
98
+ * Extract metrics from response
99
+ * @param {object} response
100
+ * @returns {object}
101
+ */
102
+ function extractMetrics (response) {
103
+ const metrics = {}
104
+ const tokenUsage = response.usageMetadata
105
+
106
+ if (!tokenUsage) return metrics
107
+
108
+ if (tokenUsage.promptTokenCount) {
109
+ metrics.inputTokens = tokenUsage.promptTokenCount
110
+ }
111
+
112
+ if (tokenUsage.candidatesTokenCount) {
113
+ metrics.outputTokens = tokenUsage.candidatesTokenCount
114
+ }
115
+
116
+ const totalTokens = tokenUsage.totalTokenCount ||
117
+ (tokenUsage.promptTokenCount || 0) + (tokenUsage.candidatesTokenCount || 0)
118
+ if (totalTokens) {
119
+ metrics.totalTokens = totalTokens
120
+ }
121
+
122
+ return metrics
123
+ }
124
+
125
+ /**
126
+ * Extract metadata from config
127
+ * @param {object} config
128
+ * @returns {object}
129
+ */
130
+ function extractMetadata (config) {
131
+ if (!config) return {}
132
+
133
+ const fieldMap = {
134
+ temperature: 'temperature',
135
+ top_p: 'topP',
136
+ top_k: 'topK',
137
+ candidate_count: 'candidateCount',
138
+ max_output_tokens: 'maxOutputTokens',
139
+ stop_sequences: 'stopSequences',
140
+ response_logprobs: 'responseLogprobs',
141
+ logprobs: 'logprobs',
142
+ presence_penalty: 'presencePenalty',
143
+ frequency_penalty: 'frequencyPenalty',
144
+ seed: 'seed',
145
+ response_mime_type: 'responseMimeType',
146
+ safety_settings: 'safetySettings',
147
+ automatic_function_calling: 'automaticFunctionCalling'
148
+ }
149
+
150
+ const metadata = {}
151
+ for (const [metadataKey, configKey] of Object.entries(fieldMap)) {
152
+ metadata[metadataKey] = config[configKey] ?? null
153
+ }
154
+
155
+ return metadata
156
+ }
157
+
158
+ /**
159
+ * Format function call message
160
+ * @param {Array} parts
161
+ * @param {Array} functionCalls
162
+ * @param {string} role
163
+ * @returns {object}
164
+ */
165
+ function formatFunctionCallMessage (parts, functionCalls, role) {
166
+ const toolCalls = functionCalls.map(part => ({
167
+ name: part.functionCall.name,
168
+ arguments: part.functionCall.args,
169
+ toolId: part.functionCall.id || '',
170
+ type: 'function_call'
171
+ }))
172
+
173
+ const textParts = extractTextParts(parts)
174
+ const content = textParts.length > 0 ? textParts.join('\n') : undefined
175
+ const message = { role, toolCalls }
176
+
177
+ if (content) message.content = content
178
+
179
+ return message
180
+ }
181
+
182
+ /**
183
+ * Format function response message
184
+ * @param {Array} functionResponses
185
+ * @param {string} role
186
+ * @returns {object}
187
+ */
188
+ function formatFunctionResponseMessage (functionResponses, role) {
189
+ const toolResults = functionResponses.map(part => ({
190
+ name: part.functionResponse.name,
191
+ result: JSON.stringify(part.functionResponse.response),
192
+ toolId: part.functionResponse.id,
193
+ type: 'function_response'
194
+ }))
195
+
196
+ return {
197
+ role,
198
+ toolResults
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Aggregate streaming chunks into a single response
204
+ * @param {Array} chunks
205
+ * @returns {object}
206
+ */
207
+ function aggregateStreamingChunks (chunks) {
208
+ const response = { candidates: [] }
209
+
210
+ for (const chunk of chunks) {
211
+ if (chunk.candidates) {
212
+ // Flatten candidates array
213
+ response.candidates.push(...chunk.candidates)
214
+ }
215
+ if (chunk.usageMetadata) {
216
+ response.usageMetadata = chunk.usageMetadata
217
+ }
218
+ }
219
+
220
+ return response
221
+ }
222
+
223
+ /**
224
+ * Format a content object into a message
225
+ * @param {object} content
226
+ * @returns {object}
227
+ */
228
+ function formatContentObject (content) {
229
+ const parts = content.parts || []
230
+ const role = determineRole(content, parts)
231
+
232
+ // Check if this is a thought/reasoning part
233
+ if (hasThoughtParts(parts)) {
234
+ return {
235
+ role: ROLES.REASONING,
236
+ content: extractTextParts(parts).join('\n')
237
+ }
238
+ }
239
+
240
+ // Check for function calls
241
+ const functionCalls = parts.filter(part => part.functionCall)
242
+ if (functionCalls.length > 0) {
243
+ return formatFunctionCallMessage(parts, functionCalls, role)
244
+ }
245
+
246
+ // Check for function responses
247
+ const functionResponses = parts.filter(part => part.functionResponse)
248
+ if (functionResponses.length > 0) {
249
+ return formatFunctionResponseMessage(functionResponses, role)
250
+ }
251
+
252
+ // Regular text content
253
+ return {
254
+ role,
255
+ content: extractTextParts(parts).join('\n')
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Format input messages from contents
261
+ * @param {*} contents
262
+ * @returns {Array}
263
+ */
264
+ function formatInputMessages (contents) {
265
+ if (!contents) return []
266
+
267
+ const contentArray = Array.isArray(contents) ? contents : [contents]
268
+ const messages = []
269
+
270
+ for (const content of contentArray) {
271
+ if (typeof content === 'string') {
272
+ messages.push({ role: ROLES.USER, content })
273
+ } else if (content.text) {
274
+ messages.push({ role: ROLES.USER, content: content.text })
275
+ } else if (content.parts) {
276
+ const message = formatContentObject(content)
277
+ if (message) messages.push(message)
278
+ } else {
279
+ messages.push({ role: ROLES.USER, content: JSON.stringify(content) })
280
+ }
281
+ }
282
+
283
+ return messages
284
+ }
285
+
286
+ /**
287
+ * Format embedding input from contents
288
+ * @param {*} contents
289
+ * @returns {Array}
290
+ */
291
+ function formatEmbeddingInput (contents) {
292
+ if (!contents) return []
293
+
294
+ const contentArray = Array.isArray(contents) ? contents : [contents]
295
+ const documents = []
296
+
297
+ for (const content of contentArray) {
298
+ if (typeof content === 'string') {
299
+ documents.push({ text: content })
300
+ } else if (content.text) {
301
+ documents.push({ text: content.text })
302
+ } else if (content.parts) {
303
+ for (const part of content.parts) {
304
+ if (typeof part === 'string') {
305
+ documents.push({ text: part })
306
+ } else if (part.text) {
307
+ documents.push({ text: part.text })
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ return documents
314
+ }
315
+
316
+ /**
317
+ * Format a non-streaming candidate into messages
318
+ * @param {object} candidate
319
+ * @returns {Array}
320
+ */
321
+ function formatNonStreamingCandidate (candidate) {
322
+ const messages = []
323
+ const content = Array.isArray(candidate) ? candidate[0].content : candidate.content
324
+
325
+ if (!content?.parts) return messages
326
+
327
+ const { parts } = content
328
+
329
+ // Check for function calls
330
+ const functionCalls = parts.filter(part => part.functionCall)
331
+ if (functionCalls.length > 0) {
332
+ messages.push(formatFunctionCallMessage(parts, functionCalls, ROLES.ASSISTANT))
333
+ return messages
334
+ }
335
+
336
+ // Check for executable code
337
+ const executableCode = parts.find(part => part.executableCode)
338
+ if (executableCode) {
339
+ messages.push({
340
+ role: ROLES.ASSISTANT,
341
+ content: JSON.stringify({
342
+ language: executableCode.executableCode.language,
343
+ code: executableCode.executableCode.code
344
+ })
345
+ })
346
+ return messages
347
+ }
348
+
349
+ // Check for code execution result
350
+ const codeExecutionResult = parts.find(part => part.codeExecutionResult)
351
+ if (codeExecutionResult) {
352
+ messages.push({
353
+ role: ROLES.ASSISTANT,
354
+ content: JSON.stringify({
355
+ outcome: codeExecutionResult.codeExecutionResult.outcome,
356
+ output: codeExecutionResult.codeExecutionResult.output
357
+ })
358
+ })
359
+ return messages
360
+ }
361
+
362
+ // Regular text content - may contain both reasoning and assistant parts
363
+ const partsByRole = groupPartsByRole(parts)
364
+
365
+ if (partsByRole.reasoning) {
366
+ messages.push({
367
+ role: ROLES.REASONING,
368
+ content: partsByRole.reasoning
369
+ })
370
+ }
371
+
372
+ if (partsByRole.assistant) {
373
+ messages.push({
374
+ role: ROLES.ASSISTANT,
375
+ content: partsByRole.assistant
376
+ })
377
+ }
378
+
379
+ return messages
380
+ }
381
+
382
+ /**
383
+ * Format streaming output from response
384
+ * @param {object} response
385
+ * @returns {Array}
386
+ */
387
+ function formatStreamingOutput (response) {
388
+ const messages = []
389
+ const messagesByRole = new Map()
390
+
391
+ for (const candidate of response.candidates) {
392
+ const content = Array.isArray(candidate) ? candidate[0].content : candidate.content
393
+ if (!content?.parts) continue
394
+
395
+ // Skip special cases in streaming (handle them as non-streaming)
396
+ if (content.parts.some(part => part.functionCall ||
397
+ part.executableCode ||
398
+ part.codeExecutionResult)) {
399
+ messages.push(...formatNonStreamingCandidate(candidate))
400
+ continue
401
+ }
402
+
403
+ // Accumulate text parts by role
404
+ const partsByRole = groupPartsByRole(content.parts)
405
+
406
+ for (const [partRole, textContent] of Object.entries(partsByRole)) {
407
+ if (!textContent) continue
408
+
409
+ if (messagesByRole.has(partRole)) {
410
+ const index = messagesByRole.get(partRole)
411
+ messages[index].content += textContent
412
+ } else {
413
+ const messageIndex = messages.length
414
+ messages.push({ role: partRole, content: textContent })
415
+ messagesByRole.set(partRole, messageIndex)
416
+ }
417
+ }
418
+ }
419
+
420
+ return messages.length > 0 ? messages : [{ content: '' }]
421
+ }
422
+
423
+ /**
424
+ * Format non-streaming output from response
425
+ * @param {object} response
426
+ * @returns {Array}
427
+ */
428
+ function formatNonStreamingOutput (response) {
429
+ const messages = []
430
+
431
+ for (const candidate of response.candidates) {
432
+ messages.push(...formatNonStreamingCandidate(candidate))
433
+ }
434
+
435
+ return messages.length > 0 ? messages : [{ content: '' }]
436
+ }
437
+
438
+ /**
439
+ * Format output messages from response
440
+ * @param {object} response
441
+ * @param {boolean} isStreaming
442
+ * @returns {Array}
443
+ */
444
+ function formatOutputMessages (response, isStreaming = false) {
445
+ if (!response?.candidates?.length) {
446
+ return [{ content: '' }]
447
+ }
448
+
449
+ if (isStreaming) {
450
+ return formatStreamingOutput(response)
451
+ }
452
+
453
+ return formatNonStreamingOutput(response)
454
+ }
455
+
456
+ /**
457
+ * Format embedding output from response
458
+ * @param {object} response
459
+ * @returns {string}
460
+ */
461
+ function formatEmbeddingOutput (response) {
462
+ if (!response?.embeddings?.length) {
463
+ return ''
464
+ }
465
+
466
+ const embeddingCount = response.embeddings.length
467
+ const firstEmbedding = response.embeddings[0]
468
+
469
+ if (firstEmbedding.values && Array.isArray(firstEmbedding.values)) {
470
+ const embeddingDim = firstEmbedding.values.length
471
+ return `[${embeddingCount} embedding(s) returned with size ${embeddingDim}]`
472
+ }
473
+
474
+ return `[${embeddingCount} embedding(s) returned]`
475
+ }
476
+
477
+ module.exports = {
478
+ getOperation,
479
+ extractMetrics,
480
+ extractMetadata,
481
+ aggregateStreamingChunks,
482
+ formatInputMessages,
483
+ formatEmbeddingInput,
484
+ formatOutputMessages,
485
+ formatEmbeddingOutput
486
+ }
@@ -174,13 +174,13 @@ class BaseLLMGeneratePlugin extends BaseLangChainLLMObsPlugin {
174
174
  class EmbeddingsEmbedQueryPlugin extends BaseLangChainLLMObsPlugin {
175
175
  static id = 'llmobs_langchain_embeddings_embed_query'
176
176
  static lcType = 'embedding'
177
- static prefix = 'tracing:apm:@langchain/core:Embeddings_embedQuery'
177
+ static prefix = 'tracing:orchestrion:@langchain/core:Embeddings_embedQuery'
178
178
  }
179
179
 
180
180
  class EmbeddingsEmbedDocumentsPlugin extends BaseLangChainLLMObsPlugin {
181
181
  static id = 'llmobs_langchain_embeddings_embed_documents'
182
182
  static lcType = 'embedding'
183
- static prefix = 'tracing:apm:@langchain/core:Embeddings_embedDocuments'
183
+ static prefix = 'tracing:orchestrion:@langchain/core:Embeddings_embedDocuments'
184
184
  }
185
185
 
186
186
  class ToolInvokePlugin extends BaseLangChainLLMObsPlugin {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
- const LLMObsPlugin = require('./base')
3
+ const LLMObsPlugin = require('../base')
4
+ const { extractChatTemplateFromInstructions, normalizePromptVariables, extractTextFromContentItem } = require('./utils')
4
5
 
5
6
  const allowedParamKeys = new Set([
6
7
  'max_output_tokens',
@@ -115,6 +116,10 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
115
116
  metrics.cacheReadTokens = cacheReadTokens
116
117
  }
117
118
  }
119
+ // Reasoning tokens - Responses API returns `output_tokens_details`, `completion_tokens_details`
120
+ const reasoningOutputObject = tokenUsage.output_tokens_details ?? tokenUsage.completion_tokens_details
121
+ const reasoningOutputTokens = reasoningOutputObject?.reasoning_tokens ?? 0
122
+ if (reasoningOutputTokens !== undefined) metrics.reasoningOutputTokens = reasoningOutputTokens
118
123
  }
119
124
 
120
125
  return metrics
@@ -221,7 +226,8 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
221
226
  #tagResponse (span, inputs, response, error) {
222
227
  // Tag metadata - use allowlist approach for request parameters
223
228
 
224
- const { input, model, ...parameters } = inputs
229
+ const { model, ...parameters } = inputs
230
+ let input = inputs.input
225
231
 
226
232
  // Create input messages
227
233
  const inputMessages = []
@@ -231,10 +237,33 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
231
237
  inputMessages.push({ role: 'system', content: inputs.instructions })
232
238
  }
233
239
 
240
+ // For reusable prompts, use response.instructions if no explicit input is provided
241
+ if (!input && inputs.prompt && response?.instructions) {
242
+ input = response.instructions
243
+ }
244
+
234
245
  // Handle input - can be string or array of mixed messages
235
246
  if (Array.isArray(input)) {
236
247
  for (const item of input) {
237
- if (item.type === 'function_call') {
248
+ if (item.type === 'message') {
249
+ // Handle instruction messages (from response.instructions for reusable prompts)
250
+ const role = item.role
251
+ if (!role) continue
252
+
253
+ let content = ''
254
+ if (Array.isArray(item.content)) {
255
+ const textParts = item.content
256
+ .map(extractTextFromContentItem)
257
+ .filter(Boolean)
258
+ content = textParts.join('')
259
+ } else if (typeof item.content === 'string') {
260
+ content = item.content
261
+ }
262
+
263
+ if (content) {
264
+ inputMessages.push({ role, content })
265
+ }
266
+ } else if (item.type === 'function_call') {
238
267
  // Function call: convert to message with tool_calls
239
268
  // Parse arguments if it's a JSON string
240
269
  let parsedArgs = item.arguments
@@ -380,6 +409,22 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
380
409
 
381
410
  this._tagger.tagLLMIO(span, inputMessages, outputMessages)
382
411
 
412
+ // Handle prompt tracking for reusable prompts
413
+ if (inputs.prompt && response?.prompt) {
414
+ const { id, version } = response.prompt // ResponsePrompt
415
+ // TODO: Add proper tagger API for prompt metadata
416
+ if (id && version) {
417
+ const normalizedVariables = normalizePromptVariables(inputs.prompt.variables)
418
+ const chatTemplate = extractChatTemplateFromInstructions(response.instructions, normalizedVariables)
419
+ this._tagger._setTag(span, '_ml_obs.meta.input.prompt', {
420
+ id,
421
+ version,
422
+ variables: normalizedVariables,
423
+ chat_template: chatTemplate
424
+ })
425
+ }
426
+ }
427
+
383
428
  const outputMetadata = {}
384
429
 
385
430
  // Add fields from response object (convert numbers to floats)
@@ -388,9 +433,6 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
388
433
  if (response.tool_choice !== undefined) outputMetadata.tool_choice = response.tool_choice
389
434
  if (response.truncation !== undefined) outputMetadata.truncation = response.truncation
390
435
  if (response.text !== undefined) outputMetadata.text = response.text
391
- if (response.usage?.output_tokens_details?.reasoning_tokens !== undefined) {
392
- outputMetadata.reasoning_tokens = response.usage.output_tokens_details.reasoning_tokens
393
- }
394
436
 
395
437
  this._tagger.tagMetadata(span, outputMetadata) // update the metadata with the output metadata
396
438
  }