newrelic 13.14.0 → 13.16.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 (36) hide show
  1. package/NEWS.md +54 -0
  2. package/THIRD_PARTY_NOTICES.md +11 -11
  3. package/lib/instrumentation/aws-sdk/v3/bedrock.js +5 -1
  4. package/lib/instrumentation/aws-sdk/v3/converse-stream-handler.js +12 -0
  5. package/lib/instrumentation/aws-sdk/v3/stream-handler.js +13 -0
  6. package/lib/instrumentations.js +0 -6
  7. package/lib/llm-events/aws-bedrock/chat-completion-summary.js +4 -2
  8. package/lib/llm-events/chat-completion-summary.js +6 -1
  9. package/lib/llm-events/google-genai/chat-completion-summary.js +3 -1
  10. package/lib/llm-events/openai/chat-completion-summary.js +4 -2
  11. package/lib/subscriber-configs.js +4 -0
  12. package/lib/subscribers/ai-monitoring/chat.js +9 -7
  13. package/lib/subscribers/base.js +20 -5
  14. package/lib/subscribers/google-genai/generate-content-stream.js +3 -0
  15. package/lib/subscribers/google-genai/generate-content.js +3 -1
  16. package/lib/subscribers/nestjs/config.js +27 -0
  17. package/lib/subscribers/nestjs/instrumentation.js +39 -0
  18. package/lib/subscribers/node-redis-client/add-command.js +13 -0
  19. package/lib/subscribers/node-redis-client/config.js +63 -0
  20. package/lib/subscribers/node-redis-client/multi.js +13 -0
  21. package/lib/subscribers/node-redis-client/send-command.js +12 -0
  22. package/lib/subscribers/openai/chat.js +11 -1
  23. package/lib/subscribers/redis/config.js +23 -0
  24. package/lib/subscribers/redis/internal-send-command.js +60 -0
  25. package/lib/subscribers/redis-client/add-command.js +46 -0
  26. package/lib/subscribers/redis-client/client-propagation.js +108 -0
  27. package/lib/subscribers/redis-client/config.js +83 -0
  28. package/lib/subscribers/redis-client/multi.js +17 -0
  29. package/lib/subscribers/redis-client/send-command.js +17 -0
  30. package/package.json +2 -2
  31. package/lib/instrumentation/@nestjs/core.js +0 -35
  32. package/lib/instrumentation/@node-redis/client.js +0 -114
  33. package/lib/instrumentation/@redis/client.js +0 -7
  34. package/lib/instrumentation/generic-pool.js +0 -33
  35. package/lib/instrumentation/redis.js +0 -103
  36. package/lib/instrumentation/superagent.js +0 -68
package/NEWS.md CHANGED
@@ -1,3 +1,54 @@
1
+ ### v13.16.0 (2026-03-11)
2
+
3
+ #### Bug fixes
4
+
5
+ * Updated `@apm-js-collab/tracing-hooks` to `0.5.0`, to allow instrumentation on windows environments. ([#3819](https://github.com/newrelic/node-newrelic/pull/3819)) ([d8f6d0f](https://github.com/newrelic/node-newrelic/commit/d8f6d0f6ac7f3ff17a667391269c41e6e57dc2fb))
6
+ * This version of `@apm-js-collab/tracing-hooks` includes the windows path fix for `@apm-js-collab/code-transformer`, [see](https://github.com/apm-js-collab/tracing-hooks/issues/19).
7
+
8
+ #### Code refactoring
9
+
10
+ * Updated `redis` v3 instrumentation to subscribe to events emitted ([#3802](https://github.com/newrelic/node-newrelic/pull/3802)) ([4d18302](https://github.com/newrelic/node-newrelic/commit/4d1830237c91d3e60c6b4e898f2c82ff131ebd57))
11
+
12
+ #### Documentation
13
+
14
+ * Updated compatibility report ([#3813](https://github.com/newrelic/node-newrelic/pull/3813)) ([27cb0a5](https://github.com/newrelic/node-newrelic/commit/27cb0a5e7a4c6047cce54483bd18d1337023efd3))
15
+
16
+ #### Tests
17
+
18
+ * Add `redis-client` and `node-redis-client` segment duration timing tests ([#3810](https://github.com/newrelic/node-newrelic/pull/3810)) ([f07bcfe](https://github.com/newrelic/node-newrelic/commit/f07bcfecf06453c846e12f030fbf7d8c055da139))
19
+
20
+ ### v13.15.0 (2026-03-09)
21
+
22
+ #### Features
23
+
24
+ * Added `time_to_first_token` for `LlmChatCompletionSummary` ([#3781](https://github.com/newrelic/node-newrelic/pull/3781)) ([737f374](https://github.com/newrelic/node-newrelic/commit/737f3740d43908be348a7795dd2c85e7a4e88cb8))
25
+ * Added `time_to_first_token` for AWS Bedrock ([#3804](https://github.com/newrelic/node-newrelic/pull/3804)) ([a993b00](https://github.com/newrelic/node-newrelic/commit/a993b00b4fa55fb2e7ba0b7748c4c21d8660b911))
26
+ * Added `time_to_first_token` for Google Gen AI ([#3785](https://github.com/newrelic/node-newrelic/pull/3785)) ([d90ee70](https://github.com/newrelic/node-newrelic/commit/d90ee709538e430395c0fb5863c344fe37009ce5))
27
+ * Added `time_to_first_token` for OpenAI ([#3782](https://github.com/newrelic/node-newrelic/pull/3782)) ([07cfd57](https://github.com/newrelic/node-newrelic/commit/07cfd570d5cc3dd77a5ae23e88587d47af3d195a))
28
+
29
+ #### Code refactoring
30
+
31
+ * Migrated `redis` 4+ instrumentation to subscribe to events emitted ([#3773](https://github.com/newrelic/node-newrelic/pull/3773)) ([add6827](https://github.com/newrelic/node-newrelic/commit/add68278d29f5f415134426d33986a59c089d71f))
32
+
33
+ #### Documentation
34
+
35
+ * Updated compatibility report ([#3800](https://github.com/newrelic/node-newrelic/pull/3800)) ([5228cdc](https://github.com/newrelic/node-newrelic/commit/5228cdc0508217fa139d8b4f8232391fd063a5fa)) ([#3795](https://github.com/newrelic/node-newrelic/pull/3795)) ([1d6fe52](https://github.com/newrelic/node-newrelic/commit/1d6fe522f0e707729a4a01f5c5e5599670136917)) ([#3791](https://github.com/newrelic/node-newrelic/pull/3791)) ([e79db5b](https://github.com/newrelic/node-newrelic/commit/e79db5bbac1500bfa49f8ac3e9dc64c409396d79)) ([#3777](https://github.com/newrelic/node-newrelic/pull/3777)) ([12fe4c6](https://github.com/newrelic/node-newrelic/commit/12fe4c6f2a7a3f08efd874ba949150c7805a8888))
36
+
37
+ #### Miscellaneous chores
38
+
39
+ * Added version comment for agent-metadata.yml ([#3779](https://github.com/newrelic/node-newrelic/pull/3779)) ([1267293](https://github.com/newrelic/node-newrelic/commit/1267293cc36c79106b7cdd84dedf2c18b8a010d9))
40
+ * Refactored nestjs instrumentation to subscriber type ([#3792](https://github.com/newrelic/node-newrelic/pull/3792)) ([f7feb25](https://github.com/newrelic/node-newrelic/commit/f7feb25a5c7349433e7de26a0f6aebb820c034f4))
41
+ * Removed `@langchain/core` peer dep from langchain openai and aws tests ([#3788](https://github.com/newrelic/node-newrelic/pull/3788)) ([e670bb1](https://github.com/newrelic/node-newrelic/commit/e670bb1f47720c61509157cf45cd200234505733))
42
+ * Removed unneccessary `generic-pool` context propagation ([#3786](https://github.com/newrelic/node-newrelic/pull/3786)) ([bdd35fb](https://github.com/newrelic/node-newrelic/commit/bdd35fbf6b57d374de84f716488498a40ece41fd))
43
+ * Removed unnecessary `superagent` context propagation ([#3798](https://github.com/newrelic/node-newrelic/pull/3798)) ([81650e1](https://github.com/newrelic/node-newrelic/commit/81650e18d7da3897f9c82098def2f34c05c78d9e))
44
+ * Updated `@apm-js-collab/tracing-hooks` to `0.4.0` ([#3787](https://github.com/newrelic/node-newrelic/pull/3787)) ([d85fb2b](https://github.com/newrelic/node-newrelic/commit/d85fb2b832bc7202e5acafa19ff59094c6e386ee))
45
+ * Updated version format in agent-metadata.yml ([#3778](https://github.com/newrelic/node-newrelic/pull/3778)) ([61759e9](https://github.com/newrelic/node-newrelic/commit/61759e9c4e9ad2796a6462b967e30bad35b3fbf7))
46
+
47
+ #### Tests
48
+
49
+ * Updated langchain vectorstore tests to use a custom vectorstore to simplify the testing process ([#3793](https://github.com/newrelic/node-newrelic/pull/3793)) ([0fd9b54](https://github.com/newrelic/node-newrelic/commit/0fd9b54c0b4782162dae769453169ad4b05d7918))
50
+ * Updated version range for langgraph tests ([#3794](https://github.com/newrelic/node-newrelic/pull/3794)) ([e02ce99](https://github.com/newrelic/node-newrelic/commit/e02ce99083d584d8b61a223c67c4509f3fa9f4ce))
51
+
1
52
  ### v13.14.0 (2026-02-24)
2
53
 
3
54
  #### Features
@@ -8397,6 +8448,9 @@ Special thanks to Ryan Copley (@RyanCopley) for the contribution.
8397
8448
 
8398
8449
 
8399
8450
 
8451
+
8452
+
8453
+
8400
8454
 
8401
8455
 
8402
8456
 
@@ -94,7 +94,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
94
94
 
95
95
  ### @apm-js-collab/tracing-hooks
96
96
 
97
- This product includes source derived from [@apm-js-collab/tracing-hooks](https://github.com/apm-js-collab/tracing-hooks) ([v0.3.1](https://github.com/apm-js-collab/tracing-hooks/tree/v0.3.1)), distributed under the [Apache-2.0 License](https://github.com/apm-js-collab/tracing-hooks/blob/v0.3.1/LICENSE):
97
+ This product includes source derived from [@apm-js-collab/tracing-hooks](https://github.com/apm-js-collab/tracing-hooks) ([v0.4.0](https://github.com/apm-js-collab/tracing-hooks/tree/v0.4.0)), distributed under the [Apache-2.0 License](https://github.com/apm-js-collab/tracing-hooks/blob/v0.4.0/LICENSE):
98
98
 
99
99
  ```
100
100
 
@@ -722,7 +722,7 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
722
722
 
723
723
  ### @newrelic/security-agent
724
724
 
725
- This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v3.0.1](https://github.com/newrelic/csec-node-agent/tree/v3.0.1)), distributed under the [UNKNOWN License](https://github.com/newrelic/csec-node-agent/blob/v3.0.1/LICENSE):
725
+ This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v3.0.2](https://github.com/newrelic/csec-node-agent/tree/v3.0.2)), distributed under the [UNKNOWN License](https://github.com/newrelic/csec-node-agent/blob/v3.0.2/LICENSE):
726
726
 
727
727
  ```
728
728
  ## New Relic Software License v1.0
@@ -1186,7 +1186,7 @@ This product includes source derived from [@opentelemetry/api](https://github.co
1186
1186
 
1187
1187
  ### @opentelemetry/core
1188
1188
 
1189
- This product includes source derived from [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.0](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.0)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.0/LICENSE):
1189
+ This product includes source derived from [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
1190
1190
 
1191
1191
  ```
1192
1192
  Apache License
@@ -1604,7 +1604,7 @@ This product includes source derived from [@opentelemetry/exporter-metrics-otlp-
1604
1604
 
1605
1605
  ### @opentelemetry/resources
1606
1606
 
1607
- This product includes source derived from [@opentelemetry/resources](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.0](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.0)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.0/LICENSE):
1607
+ This product includes source derived from [@opentelemetry/resources](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
1608
1608
 
1609
1609
  ```
1610
1610
  Apache License
@@ -2022,7 +2022,7 @@ This product includes source derived from [@opentelemetry/sdk-logs](https://gith
2022
2022
 
2023
2023
  ### @opentelemetry/sdk-metrics
2024
2024
 
2025
- This product includes source derived from [@opentelemetry/sdk-metrics](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.0](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.0)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.0/LICENSE):
2025
+ This product includes source derived from [@opentelemetry/sdk-metrics](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
2026
2026
 
2027
2027
  ```
2028
2028
  Apache License
@@ -2231,7 +2231,7 @@ This product includes source derived from [@opentelemetry/sdk-metrics](https://g
2231
2231
 
2232
2232
  ### @opentelemetry/sdk-trace-base
2233
2233
 
2234
- This product includes source derived from [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.0](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.0)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.0/LICENSE):
2234
+ This product includes source derived from [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
2235
2235
 
2236
2236
  ```
2237
2237
  Apache License
@@ -2904,7 +2904,7 @@ SOFTWARE.
2904
2904
 
2905
2905
  ### semver
2906
2906
 
2907
- This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.7.3](https://github.com/npm/node-semver/tree/v7.7.3)), distributed under the [ISC License](https://github.com/npm/node-semver/blob/v7.7.3/LICENSE):
2907
+ This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.7.4](https://github.com/npm/node-semver/tree/v7.7.4)), distributed under the [ISC License](https://github.com/npm/node-semver/blob/v7.7.4/LICENSE):
2908
2908
 
2909
2909
  ```
2910
2910
  The ISC License
@@ -2960,7 +2960,7 @@ SOFTWARE.
2960
2960
 
2961
2961
  ### @aws-sdk/client-s3
2962
2962
 
2963
- This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.982.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.982.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.982.0/LICENSE):
2963
+ This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.996.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.996.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.996.0/LICENSE):
2964
2964
 
2965
2965
  ```
2966
2966
  Apache License
@@ -3169,7 +3169,7 @@ This product includes source derived from [@aws-sdk/client-s3](https://github.co
3169
3169
 
3170
3170
  ### @aws-sdk/s3-request-presigner
3171
3171
 
3172
- This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.982.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.982.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.982.0/LICENSE):
3172
+ This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.996.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.996.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.996.0/LICENSE):
3173
3173
 
3174
3174
  ```
3175
3175
  Apache License
@@ -4510,7 +4510,7 @@ Apache License
4510
4510
 
4511
4511
  ### ajv
4512
4512
 
4513
- This product includes source derived from [ajv](https://github.com/ajv-validator/ajv) ([v6.12.6](https://github.com/ajv-validator/ajv/tree/v6.12.6)), distributed under the [MIT License](https://github.com/ajv-validator/ajv/blob/v6.12.6/LICENSE):
4513
+ This product includes source derived from [ajv](https://github.com/ajv-validator/ajv) ([v6.14.0](https://github.com/ajv-validator/ajv/tree/v6.14.0)), distributed under the [MIT License](https://github.com/ajv-validator/ajv/blob/v6.14.0/LICENSE):
4514
4514
 
4515
4515
  ```
4516
4516
  The MIT License (MIT)
@@ -5000,7 +5000,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
5000
5000
 
5001
5001
  ### eslint
5002
5002
 
5003
- This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v9.39.2](https://github.com/eslint/eslint/tree/v9.39.2)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v9.39.2/LICENSE):
5003
+ This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v9.39.3](https://github.com/eslint/eslint/tree/v9.39.3)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v9.39.3/LICENSE):
5004
5004
 
5005
5005
  ```
5006
5006
  Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
@@ -112,6 +112,7 @@ function addLlmMeta({ agent, segment, transaction }) {
112
112
  * @param {BedrockResponse} params.bedrockResponse parsed response
113
113
  * @param {Shim} params.shim shim instance
114
114
  * @param {Transaction} params.transaction active transaction
115
+ * @param {number} [params.timeOfFirstToken] For streaming requests only, timestamp of when first token was sent.
115
116
  */
116
117
  function recordChatCompletionMessages({
117
118
  agent,
@@ -120,7 +121,8 @@ function recordChatCompletionMessages({
120
121
  transaction,
121
122
  bedrockCommand,
122
123
  bedrockResponse,
123
- err
124
+ err,
125
+ timeOfFirstToken
124
126
  }) {
125
127
  if (shouldSkipInstrumentation(agent.config) === true) {
126
128
  shim.logger.debug('skipping sending of ai data')
@@ -133,6 +135,7 @@ function recordChatCompletionMessages({
133
135
  bedrockCommand,
134
136
  transaction,
135
137
  segment,
138
+ timeOfFirstToken,
136
139
  error: err !== null
137
140
  })
138
141
 
@@ -341,6 +344,7 @@ function handleResponse({ shim, err, response, segment, transaction, bedrockComm
341
344
  transaction,
342
345
  bedrockCommand,
343
346
  bedrockResponse,
347
+ timeOfFirstToken: this?.timeOfFirstToken,
344
348
  err
345
349
  })
346
350
  } else if (modelType === 'embedding') {
@@ -80,12 +80,19 @@ class ConverseStreamHandler {
80
80
  */
81
81
  onComplete
82
82
 
83
+ /**
84
+ * Timestamp of when first token was sent; needed for construction of the
85
+ * `LlmChatCompletionSummary` event.
86
+ */
87
+ timeOfFirstToken
88
+
83
89
  constructor({ stream, passThroughParams, onComplete }) {
84
90
  this.passThroughParams = passThroughParams
85
91
  this.stream = stream
86
92
  this.onComplete = onComplete
87
93
  this.segment = passThroughParams.segment
88
94
  this.generator = handleConverse
95
+ this.timeOfFirstToken = null
89
96
  }
90
97
 
91
98
  /**
@@ -97,6 +104,7 @@ class ConverseStreamHandler {
97
104
  */
98
105
  finish() {
99
106
  this.passThroughParams.response = this.response
107
+ this.passThroughParams.timeOfFirstToken = this.timeOfFirstToken
100
108
  this.onComplete(this.passThroughParams)
101
109
 
102
110
  this.segment.touch()
@@ -136,6 +144,10 @@ async function * handleConverse() {
136
144
  activeChunk = null
137
145
  }
138
146
  } else if (event.contentBlockDelta?.delta) {
147
+ // Delta represents a letter/word/token
148
+ if (!this.timeOfFirstToken) {
149
+ this.timeOfFirstToken = Date.now()
150
+ }
139
151
  // There are also deltas for tool use (stringified inputs) but we don't currently record them so we just ignore for now
140
152
  if (event.contentBlockDelta.delta.text) {
141
153
  // It seems like the first streamed chunk does not always start with a contentBlockStart message
@@ -83,11 +83,18 @@ class StreamHandler {
83
83
  */
84
84
  onComplete
85
85
 
86
+ /**
87
+ * Timestamp of when first token was sent; needed for construction of the
88
+ * `LlmChatCompletionSummary` event.
89
+ */
90
+ timeOfFirstToken
91
+
86
92
  constructor({ stream, passThroughParams, onComplete }) {
87
93
  this.passThroughParams = passThroughParams
88
94
  this.stream = stream
89
95
  this.onComplete = onComplete
90
96
  this.segment = passThroughParams.segment
97
+ this.timeOfFirstToken = null
91
98
 
92
99
  const { bedrockCommand } = passThroughParams
93
100
 
@@ -204,6 +211,7 @@ async function * handleClaude() {
204
211
 
205
212
  try {
206
213
  for await (const event of this.stream) {
214
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
207
215
  yield event
208
216
  const parsed = this.parseEvent(event)
209
217
  this.updateHeaders(parsed)
@@ -224,6 +232,7 @@ async function * handleClaude3() {
224
232
 
225
233
  try {
226
234
  for await (const event of this.stream) {
235
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
227
236
  yield event
228
237
  const parsed = this.parseEvent(event)
229
238
  this.updateHeaders(parsed)
@@ -247,6 +256,7 @@ async function * handleCohere() {
247
256
  const generations = []
248
257
  try {
249
258
  for await (const event of this.stream) {
259
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
250
260
  yield event
251
261
  const parsed = this.parseEvent(event)
252
262
  this.updateHeaders(parsed)
@@ -265,6 +275,7 @@ async function * handleCohereEmbed() {
265
275
  const embeddings = []
266
276
  try {
267
277
  for await (const event of this.stream) {
278
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
268
279
  yield event
269
280
  const parsed = this.parseEvent(event)
270
281
  this.updateHeaders(parsed)
@@ -284,6 +295,7 @@ async function * handleLlama() {
284
295
 
285
296
  try {
286
297
  for await (const event of this.stream) {
298
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
287
299
  yield event
288
300
  const parsed = this.parseEvent(event)
289
301
  this.updateHeaders(parsed)
@@ -308,6 +320,7 @@ async function * handleTitan() {
308
320
 
309
321
  try {
310
322
  for await (const event of this.stream) {
323
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
311
324
  yield event // Pass it up to the real consumer of the stream.
312
325
  const parsed = this.parseEvent(event)
313
326
  this.updateHeaders(parsed)
@@ -14,24 +14,18 @@ module.exports = function instrumentations() {
14
14
  '@grpc/grpc-js': { module: './instrumentation/grpc-js' },
15
15
  '@hapi/hapi': { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
16
16
  '@hapi/vision': { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
17
- '@nestjs/core': { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
18
- '@node-redis/client': { type: InstrumentationDescriptor.TYPE_DATASTORE },
19
17
  '@prisma/client': { type: InstrumentationDescriptor.TYPE_DATASTORE },
20
- '@redis/client': { type: InstrumentationDescriptor.TYPE_DATASTORE },
21
18
  'aws-sdk': { module: './instrumentation/aws-sdk' },
22
19
  bluebird: { type: InstrumentationDescriptor.TYPE_PROMISE },
23
20
  connect: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
24
21
  fastify: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
25
- 'generic-pool': { type: InstrumentationDescriptor.TYPE_GENERIC },
26
22
  kafkajs: { type: InstrumentationDescriptor.TYPE_MESSAGE },
27
23
  koa: { module: './instrumentation/koa' },
28
24
  memcached: { type: InstrumentationDescriptor.TYPE_DATASTORE },
29
25
  mongodb: { type: InstrumentationDescriptor.TYPE_DATASTORE },
30
26
  next: { module: './instrumentation/nextjs' },
31
27
  q: { type: null },
32
- redis: { type: InstrumentationDescriptor.TYPE_DATASTORE },
33
28
  restify: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
34
- superagent: { type: InstrumentationDescriptor.TYPE_GENERIC },
35
29
  when: { module: './instrumentation/when' },
36
30
  winston: { type: InstrumentationDescriptor.TYPE_GENERIC }
37
31
  }
@@ -16,9 +16,10 @@ module.exports = class AwsBedrockLlmChatCompletionSummary extends LlmChatComplet
16
16
  * @param {object} params.transaction Current and active transaction
17
17
  * @param {object} params.bedrockCommand AWS Bedrock Command object, represents the request
18
18
  * @param {object} params.bedrockResponse AWS Bedrock Response object
19
+ * @param {number} [params.timeOfFirstToken] For streaming requests only, timestamp of when first token was sent.
19
20
  * @param {boolean} [params.error] Set to `true` if an error occurred during creation call, omitted if no error occurred
20
21
  */
21
- constructor({ agent, segment, transaction, bedrockCommand, bedrockResponse, error }) {
22
+ constructor({ agent, segment, transaction, bedrockCommand, bedrockResponse, timeOfFirstToken, error }) {
22
23
  super({ agent,
23
24
  segment,
24
25
  transaction,
@@ -30,7 +31,8 @@ module.exports = class AwsBedrockLlmChatCompletionSummary extends LlmChatComplet
30
31
  temperature: bedrockCommand.temperature,
31
32
  maxTokens: bedrockCommand.maxTokens,
32
33
  numMsgs: (bedrockCommand.prompt.length ?? 0) + (bedrockResponse.completions.length ?? 0),
33
- finishReason: bedrockResponse?.finishReason })
34
+ finishReason: bedrockResponse?.finishReason,
35
+ timeOfFirstToken })
34
36
 
35
37
  this.setTokens(agent, bedrockCommand, bedrockResponse)
36
38
  }
@@ -28,6 +28,9 @@ const LlmEvent = require('./event-base')
28
28
  * a chat completion including system, user, and assistant messages
29
29
  * @property {string} response.organization Organization ID returned in the
30
30
  * response or response headers
31
+ * @property {number} time_to_first_token Duration in milliseconds between when
32
+ * the request was issued and the first token was received. This attribute **MUST**
33
+ * only be captured for streaming requests.
31
34
  */
32
35
  module.exports = class LlmChatCompletionSummary extends LlmEvent {
33
36
  /**
@@ -47,10 +50,11 @@ module.exports = class LlmChatCompletionSummary extends LlmEvent {
47
50
  * @param {number} params.numMsgs Number of messages comprising a
48
51
  * chat completion including system, user, and assistant messages
49
52
  * @param {string} params.finishReason Reason the model stopped generating tokens (e.g. "stop")
53
+ * @param {number} [params.timeOfFirstToken] Timestamp of when the first token was received. Only supply for streaming requests.
50
54
  * @param {boolean} [params.error] Set to `true` if an error occurred during creation call, omitted if no error occurred
51
55
  */
52
56
  constructor({ agent, segment, transaction, vendor, responseModel, requestModel, requestId,
53
- responseOrg, temperature, maxTokens, numMsgs = 0, finishReason, error }) {
57
+ responseOrg, temperature, maxTokens, numMsgs = 0, finishReason, timeOfFirstToken, error }) {
54
58
  super({ agent, segment, transaction, vendor, responseModel, requestId, error })
55
59
 
56
60
  if (requestModel) this['request.model'] = requestModel
@@ -61,6 +65,7 @@ module.exports = class LlmChatCompletionSummary extends LlmEvent {
61
65
 
62
66
  this['response.number_of_messages'] = numMsgs
63
67
  this.timestamp = segment.timer.start
68
+ if (timeOfFirstToken) this['time_to_first_token'] = timeOfFirstToken - this.timestamp
64
69
  this.duration = segment.getDurationInMillis()
65
70
  }
66
71
 
@@ -20,9 +20,10 @@ module.exports = class GoogleGenAiLlmChatCompletionSummary extends LlmChatComple
20
20
  * @param {Transaction} params.transaction Current and active transaction
21
21
  * @param {object} params.request Google Gen AI request object
22
22
  * @param {object} params.response Google Gen AI response object
23
+ * @param {number} [params.timeOfFirstToken] Timestamp of when the first token was sent, for streaming only.
23
24
  * @param {boolean} [params.error] Set to `true` if an error occurred
24
25
  */
25
- constructor({ agent, segment, transaction, request, response, error }) {
26
+ constructor({ agent, segment, transaction, request, response, timeOfFirstToken, error }) {
26
27
  super({ agent,
27
28
  segment,
28
29
  transaction,
@@ -32,6 +33,7 @@ module.exports = class GoogleGenAiLlmChatCompletionSummary extends LlmChatComple
32
33
  maxTokens: request.config?.maxOutputTokens,
33
34
  temperature: request.config?.temperature,
34
35
  vendor: 'gemini',
36
+ timeOfFirstToken,
35
37
  error })
36
38
 
37
39
  let requestMessagesLength = 0
@@ -20,9 +20,10 @@ module.exports = class OpenAiLlmChatCompletionSummary extends LlmChatCompletionS
20
20
  * @param {Transaction} params.transaction Current and active transaction.
21
21
  * @param {object} params.request OpenAI request object.
22
22
  * @param {object} params.response OpenAI response object.
23
+ * @param {number} [params.timeOfFirstToken] Timestamp of when the first token was sent, for streaming only.
23
24
  * @param {boolean} [params.error] Set to `true` if an error occurred, can be omitted in false cases.
24
25
  */
25
- constructor({ agent, segment, transaction, request, response, error }) {
26
+ constructor({ agent, segment, transaction, request, response, timeOfFirstToken, error }) {
26
27
  super({
27
28
  agent,
28
29
  segment,
@@ -34,7 +35,8 @@ module.exports = class OpenAiLlmChatCompletionSummary extends LlmChatCompletionS
34
35
  requestModel: request?.model,
35
36
  requestId: response?.headers?.['x-request-id'],
36
37
  temperature: request?.temperature,
37
- maxTokens: request?.max_tokens ?? request?.max_output_tokens
38
+ maxTokens: request?.max_tokens ?? request?.max_output_tokens,
39
+ timeOfFirstToken
38
40
  })
39
41
 
40
42
  if (request?.input) {
@@ -22,9 +22,13 @@ const subscribers = {
22
22
  ...require('./subscribers/mcp-sdk/config'),
23
23
  ...require('./subscribers/mysql/config'),
24
24
  ...require('./subscribers/mysql2/config'),
25
+ ...require('./subscribers/nestjs/config'),
26
+ ...require('./subscribers/node-redis-client/config'),
25
27
  ...require('./subscribers/openai/config'),
26
28
  ...require('./subscribers/pino/config'),
27
29
  ...require('./subscribers/pg/config'),
30
+ ...require('./subscribers/redis/config'),
31
+ ...require('./subscribers/redis-client/config'),
28
32
  ...require('./subscribers/undici/config')
29
33
  }
30
34
 
@@ -50,12 +50,13 @@ class AiMonitoringChatSubscriber extends AiMonitoringSubscriber {
50
50
  * @param {Context} params.ctx active context
51
51
  * @param {object} params.request request made to method on a given llm library
52
52
  * @param {object} params.response response from method on a given llm library
53
- * @param {object} params.err error if present
54
- * @param {object} params.metadata used only for langchain events at the moment
55
- * @param {Array} params.tags used only for langchain events at the moment
53
+ * @param {object} [params.err] error if present
54
+ * @param {number} [params.timeOfFirstToken] Timestamp of when the first streaming token was sent.
55
+ * @param {object} [params.metadata] used only for langchain events at the moment
56
+ * @param {Array} [params.tags] used only for langchain events at the moment
56
57
  * returns {object} a llm completion summary instance for the given LLM
57
58
  */
58
- createCompletionSummary({ ctx, request, response, err, metadata, tags }) {
59
+ createCompletionSummary({ ctx, request, response, err, timeOfFirstToken, metadata, tags }) {
59
60
  throw new Error('createCompletionSummary must be implemented by your subscriber')
60
61
  }
61
62
 
@@ -79,11 +80,12 @@ class AiMonitoringChatSubscriber extends AiMonitoringSubscriber {
79
80
  * @param {Context} params.ctx active context
80
81
  * @param {object} params.request request made to method on a given llm library
81
82
  * @param {object} params.response response from method on a given llm library
82
- * @param {object} params.err error if present
83
+ * @param {object} [params.err] error if present
84
+ * @param {number} [params.timeOfFirstToken] Timestamp of when the first streaming token was sent.
83
85
  * @param {object} params.metadata used only for langchain events at the moment
84
86
  * @param {Array} params.tags used only for langchain events at the moment
85
87
  */
86
- recordChatCompletionEvents({ ctx, request, response, err, metadata = {}, tags = [] }) {
88
+ recordChatCompletionEvents({ ctx, request, response, err, timeOfFirstToken, metadata = {}, tags = [] }) {
87
89
  if (!this.enabled) {
88
90
  this.logger.debug('config.ai_monitoring.enabled is set to false, not creating chat completion events.')
89
91
  return
@@ -97,7 +99,7 @@ class AiMonitoringChatSubscriber extends AiMonitoringSubscriber {
97
99
  // Explicitly end segment to provide consistent duration
98
100
  // for both LLM events and the segment
99
101
  ctx.segment.end()
100
- const completionSummary = this.createCompletionSummary({ ctx, request, response, err, metadata, tags })
102
+ const completionSummary = this.createCompletionSummary({ ctx, request, response, timeOfFirstToken, err, metadata, tags })
101
103
  this.recordEvent({ type: 'LlmChatCompletionSummary', msg: completionSummary })
102
104
 
103
105
  const messages = this.getMessages({ request, response })
@@ -42,6 +42,9 @@ const ArrayPrototypeSplice = makeCall(Array.prototype.splice)
42
42
  * @property {AsyncLocalStorage} store The async local storage instance used for context management.
43
43
  * @property {number} [callback=null] Position of callback if it needs to be wrapped for instrumentation.
44
44
  * -1 means last argument.
45
+ * @property {string} [callbackKey=null] Property name on the argument at `callback` position
46
+ * that holds the actual callback function. Used when the callback is not a direct argument but a
47
+ * property on an object argument (e.g. `args[0].callback` when callback=0 and callbackKey='callback').
45
48
  * @property {string[]} [events=[]] Set of tracing channel event names to
46
49
  * register handlers for. For any name in the set, a corresponding method
47
50
  * must exist on the subscriber instance. The method will be passed the
@@ -88,6 +91,7 @@ class Subscriber {
88
91
  this.channel = tracingChannel(this.id)
89
92
  this.store = agent.tracer._contextManager._asyncLocalStorage
90
93
  this.callback = null
94
+ this.callbackKey = null
91
95
  }
92
96
 
93
97
  shouldCreateSegment(parent) {
@@ -103,11 +107,23 @@ class Subscriber {
103
107
  * This will wrap a callback at a given position and reassign the callback argument to the wrapped one
104
108
  *
105
109
  * @param {number} position index of the callback, you can specify -1 to be the last
110
+ * @param {string} [key] property name to look up on the argument at `position` when the
111
+ * callback is not a direct argument but a property on an object argument
112
+ * (e.g. `args[0].callback` when position=0 and key='callback')
106
113
  * @param {object} context the event passed to the tracing channel hooks
107
114
  */
108
- traceCallback(position, context) {
115
+ traceCallback(position, key, context) {
109
116
  this.logger.trace('Wrapping the callback at position %s', position)
110
117
  const { asyncStart, asyncEnd, error } = this.channel
118
+
119
+ const holder = ArrayPrototypeAt(context.arguments, position)
120
+ const callback = key ? holder?.[key] : holder
121
+
122
+ if (typeof callback !== 'function') {
123
+ this.logger.trace('Callback is not present, not wrapping')
124
+ return
125
+ }
126
+
111
127
  function wrappedCallback(err, res) {
112
128
  // assigning a boolean to the context so we know that the
113
129
  // `error`, `asyncStart`, and `asyncEnd` are coming from the wrapped callback
@@ -133,9 +149,8 @@ class Subscriber {
133
149
  })
134
150
  }
135
151
 
136
- const callback = ArrayPrototypeAt(context.arguments, position)
137
- if (typeof callback !== 'function') {
138
- this.logger.trace('Callback is not present, not wrapping')
152
+ if (key) {
153
+ context.arguments[position][key] = wrappedCallback
139
154
  } else {
140
155
  ArrayPrototypeSplice(context.arguments, position, 1, wrappedCallback)
141
156
  }
@@ -254,7 +269,7 @@ class Subscriber {
254
269
 
255
270
  // only wrap the callback if a subscriber has a callback property defined
256
271
  if (this.callback !== null) {
257
- this.traceCallback(this.callback, data)
272
+ this.traceCallback(this.callback, this.callbackKey, data)
258
273
  }
259
274
  const ctx = this.agent.tracer.getContext()
260
275
  if (this.requireActiveTx && !ctx?.transaction?.isActive()) {
@@ -30,6 +30,7 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
30
30
  let cachedResult = {}
31
31
  let err
32
32
  let entireMessage = ''
33
+ let timeOfFirstToken
33
34
  response.next = async function wrappedNext(...nextArgs) {
34
35
  let result = {}
35
36
  try {
@@ -44,6 +45,7 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
44
45
  }
45
46
 
46
47
  if (result?.value?.text) {
48
+ if (!timeOfFirstToken) timeOfFirstToken = Date.now()
47
49
  entireMessage += result.value.text // readonly variable that equates to result.value.candidates[0].content.parts[0].text
48
50
  }
49
51
  } catch (streamErr) {
@@ -64,6 +66,7 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
64
66
  ctx,
65
67
  request,
66
68
  response: cachedResult,
69
+ timeOfFirstToken,
67
70
  err
68
71
  })
69
72
  }
@@ -56,10 +56,11 @@ class GoogleGenAIGenerateContentSubscriber extends AiMonitoringChatSubscriber {
56
56
  * @param {Context} params.ctx active context
57
57
  * @param {object} params.request request made to method
58
58
  * @param {object} params.response response from method
59
+ * @param {number} [params.timeOfFirstToken] Timestamp of when the first streaming token was sent.
59
60
  * @param {object} [params.err] error object if present
60
61
  * @returns {object} a llm completion summary instance for Google Gen AI
61
62
  */
62
- createCompletionSummary({ ctx, request, response = {}, err }) {
63
+ createCompletionSummary({ ctx, request, response = {}, timeOfFirstToken, err }) {
63
64
  const { transaction, segment } = ctx
64
65
  return new LlmChatCompletionSummary({
65
66
  agent: this.agent,
@@ -67,6 +68,7 @@ class GoogleGenAIGenerateContentSubscriber extends AiMonitoringChatSubscriber {
67
68
  transaction,
68
69
  request,
69
70
  response,
71
+ timeOfFirstToken,
70
72
  error: !!err
71
73
  })
72
74
  }
@@ -0,0 +1,27 @@
1
+ /*
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ module.exports = {
7
+ '@nestjs/core': [
8
+ {
9
+ path: './nestjs/instrumentation.js',
10
+ instrumentations: [
11
+ {
12
+ channelName: 'nr_unknown_error',
13
+ module: {
14
+ name: '@nestjs/core',
15
+ versionRange: '>=8.0.0',
16
+ filePath: 'exceptions/base-exception-filter.js'
17
+ },
18
+ functionQuery: {
19
+ className: 'BaseExceptionFilter',
20
+ methodName: 'handleUnknownError',
21
+ kind: 'Sync'
22
+ }
23
+ }
24
+ ]
25
+ }
26
+ ]
27
+ }