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.
- package/NEWS.md +54 -0
- package/THIRD_PARTY_NOTICES.md +11 -11
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +5 -1
- package/lib/instrumentation/aws-sdk/v3/converse-stream-handler.js +12 -0
- package/lib/instrumentation/aws-sdk/v3/stream-handler.js +13 -0
- package/lib/instrumentations.js +0 -6
- package/lib/llm-events/aws-bedrock/chat-completion-summary.js +4 -2
- package/lib/llm-events/chat-completion-summary.js +6 -1
- package/lib/llm-events/google-genai/chat-completion-summary.js +3 -1
- package/lib/llm-events/openai/chat-completion-summary.js +4 -2
- package/lib/subscriber-configs.js +4 -0
- package/lib/subscribers/ai-monitoring/chat.js +9 -7
- package/lib/subscribers/base.js +20 -5
- package/lib/subscribers/google-genai/generate-content-stream.js +3 -0
- package/lib/subscribers/google-genai/generate-content.js +3 -1
- package/lib/subscribers/nestjs/config.js +27 -0
- package/lib/subscribers/nestjs/instrumentation.js +39 -0
- package/lib/subscribers/node-redis-client/add-command.js +13 -0
- package/lib/subscribers/node-redis-client/config.js +63 -0
- package/lib/subscribers/node-redis-client/multi.js +13 -0
- package/lib/subscribers/node-redis-client/send-command.js +12 -0
- package/lib/subscribers/openai/chat.js +11 -1
- package/lib/subscribers/redis/config.js +23 -0
- package/lib/subscribers/redis/internal-send-command.js +60 -0
- package/lib/subscribers/redis-client/add-command.js +46 -0
- package/lib/subscribers/redis-client/client-propagation.js +108 -0
- package/lib/subscribers/redis-client/config.js +83 -0
- package/lib/subscribers/redis-client/multi.js +17 -0
- package/lib/subscribers/redis-client/send-command.js +17 -0
- package/package.json +2 -2
- package/lib/instrumentation/@nestjs/core.js +0 -35
- package/lib/instrumentation/@node-redis/client.js +0 -114
- package/lib/instrumentation/@redis/client.js +0 -7
- package/lib/instrumentation/generic-pool.js +0 -33
- package/lib/instrumentation/redis.js +0 -103
- 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
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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)
|
package/lib/instrumentations.js
CHANGED
|
@@ -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 {
|
|
55
|
-
* @param {
|
|
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 })
|
package/lib/subscribers/base.js
CHANGED
|
@@ -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
|
-
|
|
137
|
-
|
|
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
|
+
}
|