newrelic 12.24.0 → 12.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NEWS.md CHANGED
@@ -1,3 +1,28 @@
1
+ ### v12.25.1 (2026-02-10)
2
+
3
+ #### Security improvements
4
+
5
+ * Bumped minimum version of security agent ([#3713](https://github.com/newrelic/node-newrelic/pull/3713)) ([88b2334](https://github.com/newrelic/node-newrelic/commit/88b2334294f5b62a56599c48dc7cacb85a3e0bc2))
6
+
7
+ ### v12.25.0 (2025-07-09)
8
+
9
+ #### Features
10
+
11
+ * Instrument AWS Bedrock Converse API ([#3191](https://github.com/newrelic/node-newrelic/pull/3191)) ([16ac684](https://github.com/newrelic/node-newrelic/commit/16ac6845a82506bbdd0e51f3f9a6d05ac8016246))
12
+
13
+ #### Bug fixes
14
+
15
+ * `eventSegment.addAttribute` will not crash if null ([#3215](https://github.com/newrelic/node-newrelic/pull/3215)) ([8955f37](https://github.com/newrelic/node-newrelic/commit/8955f3737e686b4bf6beab2d4e53a1f5eb221166))
16
+ * `openai` Responses API support changed to >=v4.87.0 ([#3219](https://github.com/newrelic/node-newrelic/pull/3219)) ([19c0c70](https://github.com/newrelic/node-newrelic/commit/19c0c7011c8c877098c5a57063354f01f5f83295))
17
+
18
+ #### Documentation
19
+
20
+ * Updated compatibility report ([#3220](https://github.com/newrelic/node-newrelic/pull/3220)) ([b4350c7](https://github.com/newrelic/node-newrelic/commit/b4350c7e3a718e47d066c5c2c1f4f5f20f43e52c))
21
+
22
+ #### Miscellaneous chores
23
+
24
+ * Converted .proto to .json for bundling ([#3212](https://github.com/newrelic/node-newrelic/pull/3212)) ([1e208d4](https://github.com/newrelic/node-newrelic/commit/1e208d43acc1811674126b6aa987c895178f4fc3))
25
+
1
26
  ### v12.24.0 (2025-07-07)
2
27
 
3
28
  #### Features
@@ -7673,3 +7698,4 @@ Special thanks to Ryan Copley (@RyanCopley) for the contribution.
7673
7698
  * The agent reports transaction trace data.
7674
7699
 
7675
7700
  [mdn-async-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
7701
+
@@ -82,7 +82,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
82
82
 
83
83
  **[optionalDependencies](#optionalDependencies)**
84
84
 
85
- * [@contrast/fn-inspect](#contrastfn-inspect)
85
+ * [@newrelic/fn-inspect](#newrelicfn-inspect)
86
86
  * [@newrelic/native-metrics](#newrelicnative-metrics)
87
87
  * [@prisma/prisma-fmt-wasm](#prismaprisma-fmt-wasm)
88
88
 
@@ -95,7 +95,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
95
95
 
96
96
  ### @grpc/grpc-js
97
97
 
98
- This product includes source derived from [@grpc/grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) ([v1.13.4](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/tree/v1.13.4)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/blob/v1.13.4/LICENSE):
98
+ This product includes source derived from [@grpc/grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) ([v1.14.3](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/tree/v1.14.3)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/blob/v1.14.3/LICENSE):
99
99
 
100
100
  ```
101
101
  Apache License
@@ -513,7 +513,7 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
513
513
 
514
514
  ### @newrelic/security-agent
515
515
 
516
- This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v2.4.2](https://github.com/newrelic/csec-node-agent/tree/v2.4.2)), distributed under the [UNKNOWN License](https://github.com/newrelic/csec-node-agent/blob/v2.4.2/LICENSE):
516
+ This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v2.4.4](https://github.com/newrelic/csec-node-agent/tree/v2.4.4)), distributed under the [UNKNOWN License](https://github.com/newrelic/csec-node-agent/blob/v2.4.4/LICENSE):
517
517
 
518
518
  ```
519
519
  ## New Relic Software License v1.0
@@ -768,7 +768,7 @@ This product includes source derived from [@opentelemetry/api](https://github.co
768
768
 
769
769
  ### @opentelemetry/core
770
770
 
771
- This product includes source derived from [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js) ([v2.0.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.0.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.0.1/LICENSE):
771
+ 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):
772
772
 
773
773
  ```
774
774
  Apache License
@@ -1186,7 +1186,7 @@ This product includes source derived from [@opentelemetry/exporter-metrics-otlp-
1186
1186
 
1187
1187
  ### @opentelemetry/resources
1188
1188
 
1189
- This product includes source derived from [@opentelemetry/resources](https://github.com/open-telemetry/opentelemetry-js) ([v2.0.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.0.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.0.1/LICENSE):
1189
+ 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):
1190
1190
 
1191
1191
  ```
1192
1192
  Apache License
@@ -1395,7 +1395,7 @@ This product includes source derived from [@opentelemetry/resources](https://git
1395
1395
 
1396
1396
  ### @opentelemetry/sdk-metrics
1397
1397
 
1398
- This product includes source derived from [@opentelemetry/sdk-metrics](https://github.com/open-telemetry/opentelemetry-js) ([v2.0.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.0.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.0.1/LICENSE):
1398
+ 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):
1399
1399
 
1400
1400
  ```
1401
1401
  Apache License
@@ -1604,7 +1604,7 @@ This product includes source derived from [@opentelemetry/sdk-metrics](https://g
1604
1604
 
1605
1605
  ### @opentelemetry/sdk-trace-base
1606
1606
 
1607
- This product includes source derived from [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) ([v2.0.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.0.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.0.1/LICENSE):
1607
+ 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):
1608
1608
 
1609
1609
  ```
1610
1610
  Apache License
@@ -1902,7 +1902,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1902
1902
 
1903
1903
  ### import-in-the-middle
1904
1904
 
1905
- This product includes source derived from [import-in-the-middle](https://github.com/nodejs/import-in-the-middle) ([v1.14.0](https://github.com/nodejs/import-in-the-middle/tree/v1.14.0)), distributed under the [Apache-2.0 License](https://github.com/nodejs/import-in-the-middle/blob/v1.14.0/LICENSE):
1905
+ This product includes source derived from [import-in-the-middle](https://github.com/nodejs/import-in-the-middle) ([v1.15.0](https://github.com/nodejs/import-in-the-middle/tree/v1.15.0)), distributed under the [Apache-2.0 License](https://github.com/nodejs/import-in-the-middle/blob/v1.15.0/LICENSE):
1906
1906
 
1907
1907
  ```
1908
1908
  Apache License
@@ -2277,7 +2277,7 @@ SOFTWARE.
2277
2277
 
2278
2278
  ### semver
2279
2279
 
2280
- This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.7.2](https://github.com/npm/node-semver/tree/v7.7.2)), distributed under the [ISC License](https://github.com/npm/node-semver/blob/v7.7.2/LICENSE):
2280
+ 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):
2281
2281
 
2282
2282
  ```
2283
2283
  The ISC License
@@ -2333,7 +2333,7 @@ SOFTWARE.
2333
2333
 
2334
2334
  ### @aws-sdk/client-s3
2335
2335
 
2336
- This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.821.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.821.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.821.0/LICENSE):
2336
+ 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):
2337
2337
 
2338
2338
  ```
2339
2339
  Apache License
@@ -2542,7 +2542,7 @@ This product includes source derived from [@aws-sdk/client-s3](https://github.co
2542
2542
 
2543
2543
  ### @aws-sdk/s3-request-presigner
2544
2544
 
2545
- This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.821.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.821.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.821.0/LICENSE):
2545
+ 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):
2546
2546
 
2547
2547
  ```
2548
2548
  Apache License
@@ -3226,7 +3226,7 @@ This product includes source derived from [@newrelic/newrelic-oss-cli](https://g
3226
3226
 
3227
3227
  ### @newrelic/test-utilities
3228
3228
 
3229
- This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v9.1.0](https://github.com/newrelic/node-test-utilities/tree/v9.1.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v9.1.0/LICENSE):
3229
+ This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v9.2.0](https://github.com/newrelic/node-test-utilities/tree/v9.2.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v9.2.0/LICENSE):
3230
3230
 
3231
3231
  ```
3232
3232
  Apache License
@@ -3969,7 +3969,7 @@ THE SOFTWARE.
3969
3969
 
3970
3970
  ### aws-sdk
3971
3971
 
3972
- This product includes source derived from [aws-sdk](https://github.com/aws/aws-sdk-js) ([v2.1692.0](https://github.com/aws/aws-sdk-js/tree/v2.1692.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js/blob/v2.1692.0/LICENSE.txt):
3972
+ This product includes source derived from [aws-sdk](https://github.com/aws/aws-sdk-js) ([v2.1693.0](https://github.com/aws/aws-sdk-js/tree/v2.1693.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js/blob/v2.1693.0/LICENSE.txt):
3973
3973
 
3974
3974
  ```
3975
3975
 
@@ -4370,7 +4370,7 @@ SOFTWARE.
4370
4370
 
4371
4371
  ### eslint-plugin-jsdoc
4372
4372
 
4373
- This product includes source derived from [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) ([v50.7.1](https://github.com/gajus/eslint-plugin-jsdoc/tree/v50.7.1)), distributed under the [BSD-3-Clause License](https://github.com/gajus/eslint-plugin-jsdoc/blob/v50.7.1/LICENSE):
4373
+ This product includes source derived from [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) ([v50.8.0](https://github.com/gajus/eslint-plugin-jsdoc/tree/v50.8.0)), distributed under the [BSD-3-Clause License](https://github.com/gajus/eslint-plugin-jsdoc/blob/v50.8.0/LICENSE):
4374
4374
 
4375
4375
  ```
4376
4376
  Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/)
@@ -4402,7 +4402,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4402
4402
 
4403
4403
  ### eslint
4404
4404
 
4405
- This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v9.28.0](https://github.com/eslint/eslint/tree/v9.28.0)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v9.28.0/LICENSE):
4405
+ 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):
4406
4406
 
4407
4407
  ```
4408
4408
  Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
@@ -4429,7 +4429,7 @@ THE SOFTWARE.
4429
4429
 
4430
4430
  ### express
4431
4431
 
4432
- This product includes source derived from [express](https://github.com/expressjs/express) ([v5.1.0](https://github.com/expressjs/express/tree/v5.1.0)), distributed under the [MIT License](https://github.com/expressjs/express/blob/v5.1.0/LICENSE):
4432
+ This product includes source derived from [express](https://github.com/expressjs/express) ([v5.2.1](https://github.com/expressjs/express/tree/v5.2.1)), distributed under the [MIT License](https://github.com/expressjs/express/blob/v5.2.1/LICENSE):
4433
4433
 
4434
4434
  ```
4435
4435
  (The MIT License)
@@ -4565,7 +4565,7 @@ SOFTWARE.
4565
4565
 
4566
4566
  ### jsdoc
4567
4567
 
4568
- This product includes source derived from [jsdoc](https://github.com/jsdoc/jsdoc) ([v4.0.4](https://github.com/jsdoc/jsdoc/tree/v4.0.4)), distributed under the [Apache-2.0 License](https://github.com/jsdoc/jsdoc/blob/v4.0.4/LICENSE.md):
4568
+ This product includes source derived from [jsdoc](https://github.com/jsdoc/jsdoc) ([v4.0.5](https://github.com/jsdoc/jsdoc/tree/v4.0.5)), distributed under the [Apache-2.0 License](https://github.com/jsdoc/jsdoc/blob/v4.0.5/LICENSE.md):
4569
4569
 
4570
4570
  ```
4571
4571
  # License
@@ -4682,19 +4682,19 @@ MIT License
4682
4682
 
4683
4683
  Copyright (c) <year> <copyright holders>
4684
4684
 
4685
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
4686
- associated documentation files (the "Software"), to deal in the Software without restriction, including
4687
- without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
4688
- copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
4685
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
4686
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
4687
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
4688
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
4689
4689
  following conditions:
4690
4690
 
4691
- The above copyright notice and this permission notice shall be included in all copies or substantial
4691
+ The above copyright notice and this permission notice shall be included in all copies or substantial
4692
4692
  portions of the Software.
4693
4693
 
4694
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
4695
- LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
4696
- EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
4697
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
4694
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
4695
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
4696
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
4697
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
4698
4698
  USE OR OTHER DEALINGS IN THE SOFTWARE.
4699
4699
  ```
4700
4700
 
@@ -4729,7 +4729,7 @@ THE SOFTWARE.
4729
4729
 
4730
4730
  ### koa
4731
4731
 
4732
- This product includes source derived from [koa](https://github.com/koajs/koa) ([v2.16.1](https://github.com/koajs/koa/tree/v2.16.1)), distributed under the [MIT License](https://github.com/koajs/koa/blob/v2.16.1/LICENSE):
4732
+ This product includes source derived from [koa](https://github.com/koajs/koa) ([v2.16.3](https://github.com/koajs/koa/tree/v2.16.3)), distributed under the [MIT License](https://github.com/koajs/koa/blob/v2.16.3/LICENSE):
4733
4733
 
4734
4734
  ```
4735
4735
  (The MIT License)
@@ -5015,7 +5015,7 @@ SOFTWARE.
5015
5015
 
5016
5016
  ### protobufjs
5017
5017
 
5018
- This product includes source derived from [protobufjs](https://github.com/protobufjs/protobuf.js) ([v7.5.3](https://github.com/protobufjs/protobuf.js/tree/v7.5.3)), distributed under the [BSD-3-Clause License](https://github.com/protobufjs/protobuf.js/blob/v7.5.3/LICENSE):
5018
+ This product includes source derived from [protobufjs](https://github.com/protobufjs/protobuf.js) ([v7.5.4](https://github.com/protobufjs/protobuf.js/tree/v7.5.4)), distributed under the [BSD-3-Clause License](https://github.com/protobufjs/protobuf.js/blob/v7.5.4/LICENSE):
5019
5019
 
5020
5020
  ```
5021
5021
  This license applies to all parts of protobuf.js except those files
@@ -5123,19 +5123,19 @@ MIT License
5123
5123
 
5124
5124
  Copyright (c) <year> <copyright holders>
5125
5125
 
5126
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
5127
- associated documentation files (the "Software"), to deal in the Software without restriction, including
5128
- without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5129
- copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
5126
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
5127
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
5128
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5129
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
5130
5130
  following conditions:
5131
5131
 
5132
- The above copyright notice and this permission notice shall be included in all copies or substantial
5132
+ The above copyright notice and this permission notice shall be included in all copies or substantial
5133
5133
  portions of the Software.
5134
5134
 
5135
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
5136
- LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
5137
- EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
5138
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
5135
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
5136
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
5137
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
5138
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
5139
5139
  USE OR OTHER DEALINGS IN THE SOFTWARE.
5140
5140
  ```
5141
5141
 
@@ -5235,9 +5235,9 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5235
5235
 
5236
5236
  ## optionalDependencies
5237
5237
 
5238
- ### @contrast/fn-inspect
5238
+ ### @newrelic/fn-inspect
5239
5239
 
5240
- This product includes source derived from [@contrast/fn-inspect](https://github.com/Contrast-Security-Inc/node-fn-inspect) ([v4.3.0](https://github.com/Contrast-Security-Inc/node-fn-inspect/tree/v4.3.0)), distributed under the [MIT License](https://github.com/Contrast-Security-Inc/node-fn-inspect/blob/v4.3.0/LICENSE):
5240
+ This product includes source derived from [@newrelic/fn-inspect](https://github.com/newrelic-forks/node-fn-inspect) ([v4.4.0](https://github.com/newrelic-forks/node-fn-inspect/tree/v4.4.0)), distributed under the [MIT License](https://github.com/newrelic-forks/node-fn-inspect/blob/v4.4.0/LICENSE):
5241
5241
 
5242
5242
  ```
5243
5243
  Copyright 2022 Contrast Security, Inc
package/esm-loader.mjs CHANGED
@@ -3,4 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
- export * from 'import-in-the-middle/hook.mjs'
6
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
7
+ import { register } from 'node:module'
8
+
9
+ register('import-in-the-middle/hook.mjs', import.meta.url, {})
@@ -22,7 +22,7 @@ const connectionStates = require('./connection/states')
22
22
 
23
23
  const PROTO_OPTIONS = { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }
24
24
 
25
- const PROTO_DEFINITION_PATH = __dirname + '../../../lib/grpc/endpoints/infinite-tracing/v1.proto'
25
+ const PROTO_DEFINITION = require('./endpoints/infinite-tracing/v1.json')
26
26
 
27
27
  const DEFAULT_RECONNECT_DELAY_MS = 15 * 1000
28
28
 
@@ -369,7 +369,7 @@ class GrpcConnection extends EventEmitter {
369
369
  const endpoint = this._endpoint
370
370
  logger.trace('Creating gRPC client for: ', endpoint)
371
371
 
372
- const packageDefinition = protoLoader.loadSync(PROTO_DEFINITION_PATH, PROTO_OPTIONS)
372
+ const packageDefinition = protoLoader.fromJSON(PROTO_DEFINITION, PROTO_OPTIONS)
373
373
 
374
374
  const protoDescriptor = grpc.loadPackageDefinition(packageDefinition)
375
375
 
@@ -0,0 +1,10 @@
1
+ The `.proto` files in this directory are provided by New Relic internal
2
+ specifications.
3
+
4
+ The `.json` files in this directory are JSON descriptor files derived from
5
+ the equivalent `.proto` files. This is accomplished by utilizing the CLI
6
+ tool provided by `protobufjs`. As an example:
7
+
8
+ ```sh
9
+ npx --package=protobufjs-cli -c 'pbjs --keep-case v1.proto' > v1.json
10
+ ```
@@ -0,0 +1,106 @@
1
+ {
2
+ "nested": {
3
+ "com": {
4
+ "nested": {
5
+ "newrelic": {
6
+ "nested": {
7
+ "trace": {
8
+ "nested": {
9
+ "v1": {
10
+ "nested": {
11
+ "IngestService": {
12
+ "methods": {
13
+ "RecordSpan": {
14
+ "requestType": "Span",
15
+ "requestStream": true,
16
+ "responseType": "RecordStatus",
17
+ "responseStream": true
18
+ },
19
+ "RecordSpanBatch": {
20
+ "requestType": "SpanBatch",
21
+ "requestStream": true,
22
+ "responseType": "RecordStatus",
23
+ "responseStream": true
24
+ }
25
+ }
26
+ },
27
+ "SpanBatch": {
28
+ "fields": {
29
+ "spans": {
30
+ "rule": "repeated",
31
+ "type": "Span",
32
+ "id": 1
33
+ }
34
+ }
35
+ },
36
+ "Span": {
37
+ "fields": {
38
+ "trace_id": {
39
+ "type": "string",
40
+ "id": 1
41
+ },
42
+ "intrinsics": {
43
+ "keyType": "string",
44
+ "type": "AttributeValue",
45
+ "id": 2
46
+ },
47
+ "user_attributes": {
48
+ "keyType": "string",
49
+ "type": "AttributeValue",
50
+ "id": 3
51
+ },
52
+ "agent_attributes": {
53
+ "keyType": "string",
54
+ "type": "AttributeValue",
55
+ "id": 4
56
+ }
57
+ }
58
+ },
59
+ "AttributeValue": {
60
+ "oneofs": {
61
+ "value": {
62
+ "oneof": [
63
+ "string_value",
64
+ "bool_value",
65
+ "int_value",
66
+ "double_value"
67
+ ]
68
+ }
69
+ },
70
+ "fields": {
71
+ "string_value": {
72
+ "type": "string",
73
+ "id": 1
74
+ },
75
+ "bool_value": {
76
+ "type": "bool",
77
+ "id": 2
78
+ },
79
+ "int_value": {
80
+ "type": "int64",
81
+ "id": 3
82
+ },
83
+ "double_value": {
84
+ "type": "double",
85
+ "id": 4
86
+ }
87
+ }
88
+ },
89
+ "RecordStatus": {
90
+ "fields": {
91
+ "messages_seen": {
92
+ "type": "uint64",
93
+ "id": 1
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
@@ -19,9 +19,27 @@ const { AI } = require('../../../metrics/names')
19
19
  const { RecorderSpec } = require('../../../shim/specs')
20
20
  const InstrumentationDescriptor = require('../../../instrumentation-descriptor')
21
21
  const { extractLlmContext } = require('../../../util/llm-utils')
22
+ const ConverseStreamHandler = require('../../../llm-events/aws-bedrock/converse-stream-handler')
22
23
 
23
24
  let TRACKING_METRIC
24
25
 
26
+ const INSTRUMENTED_COMMANDS = new Set([
27
+ 'InvokeModelCommand',
28
+ 'InvokeModelWithResponseStreamCommand',
29
+ 'ConverseCommand',
30
+ 'ConverseStreamCommand'
31
+ ])
32
+
33
+ const STREAMING_COMMANDS = new Set([
34
+ 'InvokeModelWithResponseStreamCommand',
35
+ 'ConverseStreamCommand'
36
+ ])
37
+
38
+ const CONVERSE_COMMANDS = new Set([
39
+ 'ConverseCommand',
40
+ 'ConverseStreamCommand'
41
+ ])
42
+
25
43
  /**
26
44
  * Helper to determine if we should instrument the bedrock middleware call
27
45
  *
@@ -42,7 +60,7 @@ function shouldSkipInstrumentation(config) {
42
60
  */
43
61
  function isStreamingEnabled({ commandName, config }) {
44
62
  return (
45
- commandName === 'InvokeModelWithResponseStreamCommand' &&
63
+ STREAMING_COMMANDS.has(commandName) &&
46
64
  config.ai_monitoring?.streaming?.enabled
47
65
  )
48
66
  }
@@ -242,6 +260,7 @@ function createBedrockResponse({ bedrockCommand, response, err }) {
242
260
  function getBedrockSpec({ commandName }, shim, _original, _name, args) {
243
261
  const { agent } = shim
244
262
  const { input } = args[0]
263
+ const isConverse = CONVERSE_COMMANDS.has(commandName)
245
264
  const bedrockCommand = new BedrockCommand(input)
246
265
  const { modelType } = bedrockCommand
247
266
 
@@ -261,17 +280,33 @@ function getBedrockSpec({ commandName }, shim, _original, _name, args) {
261
280
 
262
281
  if (err && !response) {
263
282
  handleResponse(passThroughParams)
264
- } else if (response.output.body instanceof Uint8Array) {
283
+ } else if (response.output.body instanceof Uint8Array || 'output' in response.output) {
265
284
  // non-streamed response
266
285
  handleResponse(passThroughParams)
267
286
  } else if (isStreamingEnabled({ commandName, config: agent.config })) {
268
287
  // stream response
269
- const handler = new StreamHandler({
270
- stream: response.output.body,
271
- onComplete: handleResponse,
272
- passThroughParams
273
- })
274
- response.output.body = handler.generator(handleResponse)
288
+ if (isConverse) {
289
+ const handler = new ConverseStreamHandler({
290
+ stream: response.output.stream,
291
+ onComplete: handleResponse,
292
+ passThroughParams
293
+ })
294
+ response.output = {
295
+ ...response.output ?? {},
296
+ output: {
297
+ message: {
298
+ content: handler.generator(handleResponse)
299
+ }
300
+ }
301
+ }
302
+ } else {
303
+ const handler = new StreamHandler({
304
+ stream: response.output.body,
305
+ onComplete: handleResponse,
306
+ passThroughParams
307
+ })
308
+ response.output.body = handler.generator(handleResponse)
309
+ }
275
310
  } else if (!isStreamingEnabled({ commandName, config: agent.config })) {
276
311
  shim.logger.warn(
277
312
  'ai_monitoring.streaming.enabled is set to `false`, stream will not be instrumented.'
@@ -285,6 +320,11 @@ function getBedrockSpec({ commandName }, shim, _original, _name, args) {
285
320
 
286
321
  function handleResponse({ shim, err, response, segment, transaction, bedrockCommand, modelType }) {
287
322
  const { agent } = shim
323
+ // For the ConverseStreamHandler only, response.output does not
324
+ // contain stopReason yet, so we set it here.
325
+ if (response?.output && this?.stopReason) {
326
+ response.output.stopReason = this.stopReason
327
+ }
288
328
  const bedrockResponse = createBedrockResponse({ bedrockCommand, response, err })
289
329
 
290
330
  addLlmMeta({ agent, segment, transaction })
@@ -319,13 +359,12 @@ function handleResponse({ shim, err, response, segment, transaction, bedrockComm
319
359
  * @param {object} config AWS configuration object
320
360
  * @param {function} next the next middleware function in stack
321
361
  * @param {object} context AWS client context info
362
+ *
363
+ * @returns {function} the next middleware function in stack
322
364
  */
323
365
  function bedrockMiddleware(shim, config, next, context) {
324
366
  const { commandName } = context
325
- if (
326
- commandName === 'InvokeModelCommand' ||
327
- commandName === 'InvokeModelWithResponseStreamCommand'
328
- ) {
367
+ if (INSTRUMENTED_COMMANDS.has(commandName)) {
329
368
  return shim.record(next, getBedrockSpec.bind(null, { commandName }))
330
369
  }
331
370
 
@@ -394,10 +394,10 @@ module.exports = function initialize(agent, openai, moduleName, shim) {
394
394
  }
395
395
  )
396
396
 
397
- // New API introduced in openai@5.0.0
397
+ // New API introduced in openai@4.87.0
398
398
  // Also instruments chat completion creation
399
399
  // and creates the LLM events
400
- if (semver.gte(shim.pkgVersion, '5.0.0')) {
400
+ if (semver.gte(shim.pkgVersion, '4.87.0')) {
401
401
  shim.record(openaiClient.Responses.prototype, 'create', function wrapCreate(shim, create, name, args) {
402
402
  const [request] = args
403
403
 
@@ -296,7 +296,6 @@ module.exports = function initialize(shim, when) {
296
296
  ret = agent.tracer.bindFunction(fn, context, true).apply(this, arguments)
297
297
  } finally {
298
298
  if (ret && typeof ret.then === 'function') {
299
- // eslint-disable-next-line sonarjs/no-dead-store
300
299
  ret = ctx.next[symbols.context].continue(ret)
301
300
  }
302
301
  }
@@ -5,25 +5,43 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const { stringifyClaudeChunkedMessage } = require('./utils')
8
+ const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./utils')
9
9
 
10
10
  /**
11
- * Parses an AWS invoke command instance into a re-usable entity.
11
+ * Parses an AWS Bedrock command instance into a re-usable entity,
12
+ * unifying logic for both InvokeModel and Converse commands.
12
13
  */
13
14
  class BedrockCommand {
14
15
  #input
15
- #body
16
16
  #modelId
17
+ #body
18
+ #messages
19
+ #isConverseCommand
17
20
 
18
21
  /**
19
- * @param {object} input The `input` property from an InvokeModelCommand or
20
- * InvokeModelWithResponseStreamCommand instance that is used for the
21
- * conversation.
22
+ * @param {object} input The `input` property from an InvokeModelCommand,
23
+ * InvokeModelWithResponseStreamCommand, ConverseCommand, or
24
+ * ConverseStreamCommand instance.
22
25
  */
23
26
  constructor(input) {
24
27
  this.#input = input
25
- this.#body = JSON.parse(this.#input.body)
26
28
  this.#modelId = this.#input.modelId?.toLowerCase() ?? ''
29
+
30
+ if (Object.hasOwn(input, 'body') === true) {
31
+ this.#body = JSON.parse(this.#input.body)
32
+ this.#isConverseCommand = false
33
+ } else if (Object.hasOwn(input, 'messages') === true) {
34
+ this.#messages = input.messages
35
+ this.#isConverseCommand = true
36
+ }
37
+ }
38
+
39
+ /**
40
+ *
41
+ * @returns {boolean} True if the command is from the Converse API.
42
+ */
43
+ get isConverse() {
44
+ return this.#isConverseCommand
27
45
  }
28
46
 
29
47
  /**
@@ -32,17 +50,24 @@ class BedrockCommand {
32
50
  * @returns {number|undefined}
33
51
  */
34
52
  get maxTokens() {
35
- let result
36
- if (this.isClaude() === true) {
37
- result = this.#body.max_tokens_to_sample
38
- } else if (this.isClaude3() === true || this.isCohere() === true) {
39
- result = this.#body.max_tokens
40
- } else if (this.isLlama() === true) {
41
- result = this.#body.max_gen_length
42
- } else if (this.isTitan() === true) {
43
- result = this.#body.textGenerationConfig?.maxTokenCount
53
+ if (this.#isConverseCommand) {
54
+ // Logic for Converse
55
+ return this.#input?.inferenceConfig?.maxTokens
56
+ } else {
57
+ // Logic for InvokeModel
58
+ if (this.isClaude() === true) {
59
+ return this.#body.max_tokens_to_sample
60
+ }
61
+ if (this.isClaude3() === true || this.isCohere() === true) {
62
+ return this.#body.max_tokens
63
+ }
64
+ if (this.isLlama() === true) {
65
+ return this.#body.max_gen_length
66
+ }
67
+ if (this.isTitan() === true) {
68
+ return this.#body.textGenerationConfig?.maxTokenCount
69
+ }
44
70
  }
45
- return result
46
71
  }
47
72
 
48
73
  /**
@@ -60,40 +85,53 @@ class BedrockCommand {
60
85
  * @returns {string} One of `embedding` or `completion`.
61
86
  */
62
87
  get modelType() {
63
- // At the moment, this is a simple check. If Amazon ever introduces a
64
- // complex identifier, we can implement a more complicated check.
88
+ // This logic is common to both command types
65
89
  return this.#modelId.toLowerCase().includes('embed') ? 'embedding' : 'completion'
66
90
  }
67
91
 
68
92
  /**
69
93
  * The question posed to the LLM.
70
94
  *
71
- * @returns {object[]} The array of context messages passed to the LLM (or a single user prompt for legacy "non-chat" models)
95
+ * @returns {string|string[]|object[]}
72
96
  */
73
97
  get prompt() {
74
- if (this.isTitan() === true || this.isTitanEmbed() === true) {
75
- return [
76
- {
77
- role: 'user',
78
- content: this.#body.inputText
98
+ if (this.#isConverseCommand) {
99
+ // Logic for Converse
100
+ const result = []
101
+ for (const message of this.#messages) {
102
+ // The `message.content` field is an array of ContentBlock objects.
103
+ // For text messages, the structure is: content: [{ text: '...' }]
104
+ if (Array.isArray(message?.content) === true) {
105
+ result.push({
106
+ role: message.role,
107
+ content: stringifyConverseChunkedMessage(message.content)
108
+ })
79
109
  }
80
- ]
81
- } else if (this.isCohereEmbed() === true) {
82
- return [
83
- {
84
- role: 'user',
85
- content: this.#body.texts.join(' ')
86
- }
87
- ]
88
- } else if (
89
- this.isClaudeTextCompletionApi() === true ||
90
- this.isCohere() === true ||
91
- this.isLlama() === true
110
+ }
111
+ return result
112
+ }
113
+
114
+ // Logic for InvokeModel
115
+ if (this.isTitan() || this.isTitanEmbed()) {
116
+ return [{ role: 'user', content: this.#body.inputText }]
117
+ }
118
+
119
+ if (this.isCohereEmbed()) {
120
+ return [{ role: 'user', content: this.#body.texts.join(' ') }]
121
+ }
122
+
123
+ if (
124
+ this.isClaudeTextCompletionApi(this.#body) ||
125
+ this.isCohere() ||
126
+ this.isLlama()
92
127
  ) {
93
128
  return [{ role: 'user', content: this.#body.prompt }]
94
- } else if (this.isClaudeMessagesApi() === true) {
129
+ }
130
+
131
+ if (this.isClaudeMessagesApi(this.#body)) {
95
132
  return normalizeClaude3Messages(this.#body?.messages)
96
133
  }
134
+
97
135
  return []
98
136
  }
99
137
 
@@ -101,20 +139,25 @@ class BedrockCommand {
101
139
  * @returns {number|undefined}
102
140
  */
103
141
  get temperature() {
104
- let result
142
+ if (this.#isConverseCommand) {
143
+ // Logic for Converse
144
+ return this.#input?.inferenceConfig?.temperature
145
+ }
146
+ // Logic for InvokeModel
105
147
  if (this.isTitan() === true) {
106
- result = this.#body.textGenerationConfig?.temperature
107
- } else if (
148
+ return this.#body.textGenerationConfig?.temperature
149
+ }
150
+ if (
108
151
  this.isClaude() === true ||
109
- this.isClaude3() === true ||
110
- this.isCohere() === true ||
111
- this.isLlama() === true
152
+ this.isClaude3() === true ||
153
+ this.isCohere() === true ||
154
+ this.isLlama() === true
112
155
  ) {
113
- result = this.#body.temperature
156
+ return this.#body.temperature
114
157
  }
115
- return result
116
158
  }
117
159
 
160
+ // Helper methods that depend on modelId (common to both types)
118
161
  isClaude() {
119
162
  return this.#modelId.split('.').slice(-2).join('.').startsWith('anthropic.claude-v')
120
163
  }
@@ -143,21 +186,22 @@ class BedrockCommand {
143
186
  return this.#modelId.startsWith('amazon.titan-embed')
144
187
  }
145
188
 
146
- isClaudeMessagesApi() {
147
- return (this.isClaude3() === true || this.isClaude() === true) && 'messages' in this.#body
189
+ isClaudeMessagesApi(body) {
190
+ return (this.isClaude3() === true || this.isClaude() === true) && 'messages' in body
148
191
  }
149
192
 
150
- isClaudeTextCompletionApi() {
151
- return this.isClaude() === true && 'prompt' in this.#body
193
+ isClaudeTextCompletionApi(body) {
194
+ return this.isClaude() === true && 'prompt' in body
152
195
  }
153
196
  }
154
197
 
155
198
  /**
156
- * Claude v3 requests in Bedrock can have two different "chat" flavors. This function normalizes them into a consistent
199
+ * Claude v3 requests in Bedrock can have two different "chat" flavors.
200
+ * This function normalizes them into a consistent
157
201
  * format per the AIM agent spec
158
202
  *
159
- * @param messages - The raw array of messages passed to the invoke API
160
- * @returns {number|undefined} - The normalized messages
203
+ * @param {Array<object>} messages - The raw array of messages passed to the invoke API
204
+ * @returns {Array<object>} - The normalized messages
161
205
  */
162
206
  function normalizeClaude3Messages(messages) {
163
207
  const result = []
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const { stringifyClaudeChunkedMessage } = require('./utils')
8
+ const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./utils')
9
9
 
10
10
  /**
11
11
  * @typedef {object} AwsBedrockMiddlewareResponse
@@ -18,60 +18,49 @@ const { stringifyClaudeChunkedMessage } = require('./utils')
18
18
  */
19
19
 
20
20
  /**
21
- * Represents a response from the Bedrock API. Given that the API response
22
- * has as many different shapes as the number of models it supports, and the
23
- * fact that responses require byte array processing, this object provides
24
- * an abstraction that normalizes responses into a known interface and
25
- * simplifies accessing desired fields.
21
+ * Represents a response from the Bedrock API, handling both standard and Converse API responses.
22
+ * This object provides an abstraction that normalizes responses into a known interface and
23
+ * simplifies accessing desired fields, accommodating the varied shapes of Bedrock API responses.
26
24
  */
27
25
  class BedrockResponse {
26
+ #originalResponse
28
27
  #innerResponse
29
28
  #innerOutput
30
29
  #parsedBody
30
+ #output
31
31
  #command
32
32
  #completions = []
33
33
  #id
34
+ #isConverse
34
35
 
35
36
  /**
36
37
  * @param {object} params
37
38
  * @param {AwsBedrockMiddlewareResponse} params.response
38
39
  * @param {BedrockCommand} params.bedrockCommand
39
- * @param params.isError
40
+ * @param {boolean} params.isError
40
41
  */
41
42
  constructor({ response, bedrockCommand, isError = false }) {
43
+ this.#originalResponse = response
42
44
  this.#innerResponse = isError ? response.$response : response.response
43
45
  this.#command = bedrockCommand
44
46
  this.isError = isError
47
+ this.#isConverse = this.#command.isConverse ?? false
45
48
 
46
49
  if (this.isError) {
47
50
  return
48
51
  }
49
52
 
50
- this.#innerOutput = response.output
51
-
52
- const json = new TextDecoder().decode(this.#innerOutput.body)
53
- this.#parsedBody = JSON.parse(json)
54
-
55
- const cmd = this.#command
56
- const body = this.#parsedBody
57
- if (cmd.isClaude() === true) {
58
- // TODO: can we make this thing give more than one completion?
59
- body.completion && this.#completions.push(body.completion)
60
- } else if (cmd.isClaude3() === true) {
61
- if (body?.type === 'message_stop') {
62
- // Streamed response
63
- this.#completions = body.completions
64
- } else {
65
- this.#completions = [stringifyClaudeChunkedMessage(body?.content)]
66
- }
67
- this.#id = body.id
68
- } else if (cmd.isCohere() === true) {
69
- this.#completions = body.generations?.map((g) => g.text) ?? []
70
- this.#id = body.id
71
- } else if (cmd.isLlama() === true) {
72
- body.generation && this.#completions.push(body.generation)
73
- } else if (cmd.isTitan() === true) {
74
- this.#completions = body.results?.map((r) => r.outputText) ?? []
53
+ if (this.#isConverse) {
54
+ this.#innerOutput = response.output.output
55
+ this.#output = this.#innerOutput.message
56
+ } else {
57
+ this.#innerOutput = response.output
58
+ const json = new TextDecoder().decode(this.#innerOutput.body)
59
+ this.#parsedBody = JSON.parse(json)
60
+
61
+ const cmd = this.#command
62
+ const body = this.#parsedBody
63
+ this.#extractCompletionsAndId(cmd, body)
75
64
  }
76
65
  }
77
66
 
@@ -82,32 +71,40 @@ class BedrockResponse {
82
71
  * prompt.
83
72
  */
84
73
  get completions() {
85
- return this.#completions
74
+ if (this.#isConverse === false) {
75
+ return this.#completions
76
+ }
77
+ const content = this.#output?.content
78
+ if (!content) return []
79
+ return [typeof content === 'string' ? content : stringifyConverseChunkedMessage(content)]
86
80
  }
87
81
 
88
82
  /**
89
83
  * The reason the model has given for finishing the response.
90
84
  *
91
- * @returns {string|*}
85
+ * @returns {string|undefined}
92
86
  */
93
87
  get finishReason() {
94
- let result
95
-
96
88
  if (this.isError) {
97
- return result
89
+ return undefined
90
+ }
91
+ if (this.#isConverse) {
92
+ return this.#originalResponse.output.stopReason
98
93
  }
99
-
100
94
  const cmd = this.#command
101
95
  if (cmd.isClaude() === true || cmd.isClaude3() === true) {
102
- result = this.#parsedBody.stop_reason
103
- } else if (cmd.isCohere() === true) {
104
- result = this.#parsedBody.generations?.find((r) => r.finish_reason !== null)?.finish_reason
105
- } else if (cmd.isLlama() === true) {
106
- result = this.#parsedBody.stop_reason
107
- } else if (cmd.isTitan() === true) {
108
- result = this.#parsedBody.results?.find((r) => r.completionReason !== null)?.completionReason
96
+ return this.#parsedBody.stop_reason
97
+ }
98
+ if (cmd.isCohere() === true) {
99
+ return this.#parsedBody.generations?.find((r) => r.finish_reason !== null)?.finish_reason
100
+ }
101
+ if (cmd.isLlama() === true) {
102
+ return this.#parsedBody.stop_reason
109
103
  }
110
- return result
104
+ if (cmd.isTitan() === true) {
105
+ return this.#parsedBody.results?.find((r) => r.completionReason !== null)?.completionReason
106
+ }
107
+ return undefined
111
108
  }
112
109
 
113
110
  /**
@@ -125,7 +122,8 @@ class BedrockResponse {
125
122
  * @returns {string|undefined}
126
123
  */
127
124
  get id() {
128
- return this.#id
125
+ // For Converse API, the requestId serves as the id.
126
+ return this.#isConverse ? this.requestId : this.#id
129
127
  }
130
128
 
131
129
  /**
@@ -146,12 +144,30 @@ class BedrockResponse {
146
144
  return this.#innerResponse.statusCode
147
145
  }
148
146
 
149
- #tokenCount(headerName) {
150
- const headerVal = this.headers?.[headerName]
151
- if (headerVal != null) {
152
- return parseInt(headerVal, 10)
147
+ /**
148
+ * Extracts and sets #completions and #id from the InvokeModel response body.
149
+ * @param {BedrockCommand} cmd
150
+ * @param {*} body
151
+ */
152
+ #extractCompletionsAndId(cmd, body) {
153
+ if (cmd.isClaude() === true) {
154
+ body.completion && this.#completions.push(body.completion)
155
+ } else if (cmd.isClaude3() === true) {
156
+ if (body?.type === 'message_stop') {
157
+ // Streamed response
158
+ this.#completions = body.completions
159
+ } else {
160
+ this.#completions = [stringifyClaudeChunkedMessage(body?.content)]
161
+ }
162
+ this.#id = body.id
163
+ } else if (cmd.isCohere() === true) {
164
+ this.#completions = body.generations?.map((g) => g.text) ?? []
165
+ this.#id = body.id
166
+ } else if (cmd.isLlama() === true) {
167
+ body.generation && this.#completions.push(body.generation)
168
+ } else if (cmd.isTitan() === true) {
169
+ this.#completions = body.results?.map((r) => r.outputText) ?? []
153
170
  }
154
- return undefined
155
171
  }
156
172
  }
157
173
 
@@ -57,7 +57,7 @@ class LlmChatCompletionMessage extends LlmEvent {
57
57
 
58
58
  #setId(index) {
59
59
  const cmd = this.bedrockCommand
60
- if (cmd.isTitan() === true || cmd.isClaude() === true) {
60
+ if (cmd.isConverse || cmd.isTitan() === true || cmd.isClaude() === true) {
61
61
  this.id = `${this.id}-${index}`
62
62
  } else if (cmd.isCohere() === true) {
63
63
  this.id = `${this.bedrockResponse.id || this.id}-${index}`
@@ -27,16 +27,14 @@ class LlmChatCompletionSummary extends LlmEvent {
27
27
  const { segment, isError } = params
28
28
  this.error = isError
29
29
  this.duration = segment.getDurationInMillis()
30
- this['request.max_tokens'] = this.bedrockCommand.maxTokens
31
-
32
- const nm = 'response.number_of_messages'
33
- const cfr = 'response.choices.finish_reason'
34
- const rt = 'request.temperature'
35
30
 
36
31
  const cmd = this.bedrockCommand
37
- this[cfr] = this.bedrockResponse.finishReason
38
- this[rt] = cmd.temperature
39
- this[nm] = (this.bedrockCommand.prompt.length) + this.bedrockResponse.completions.length
32
+ const res = this.bedrockResponse
33
+
34
+ this['request.max_tokens'] = cmd.maxTokens
35
+ this['response.choices.finish_reason'] = res.finishReason
36
+ this['request.temperature'] = cmd.temperature
37
+ this['response.number_of_messages'] = (cmd.prompt.length ?? 0) + (res.completions.length ?? 0)
40
38
  }
41
39
  }
42
40
 
@@ -0,0 +1,155 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ /**
9
+ * A stream handler processes a streamed response from the Bedrock API by
10
+ * intercepting each stream event, passing that event unmodified up to the
11
+ * consuming client, and then collecting the information into an "API response"
12
+ * object that satisfies the requirements of {@link BedrockResponse}. Once the
13
+ * stream has reached its end, the handler applies some finalizations to the
14
+ * compiled response object, updates the pass through parameters with the new
15
+ * object, and then invokes the final response handler passed in through
16
+ * `onComplete`.
17
+ */
18
+ class ConverseStreamHandler {
19
+ // We have to make most of the properties of this object public so that
20
+ // the at-construction-time attached generator function can access them
21
+ // through the `this` reference. See https://jrfom.com/posts/2023/10/31/js-classes/
22
+ // for details on why this is necessary.
23
+
24
+ /**
25
+ * The parameters to pass through to the `onComplete` function once the
26
+ * stream has been processed. The `response` property on this object will
27
+ * be overwritten with the response object that has been compiled by the
28
+ * stream handler.
29
+ */
30
+ passThroughParams
31
+
32
+ /**
33
+ * The original async iterable returned from the AWS SDK.
34
+ */
35
+ stream
36
+
37
+ /**
38
+ * The New Relic agent's internal trace segment. The `.touch` method will be
39
+ * used to mark the end time of the stream processing.
40
+ */
41
+ segment
42
+
43
+ /**
44
+ * For streaming messages, we want to capture the effective message chunks like we would see in the non-streaming API
45
+ */
46
+ observedChunks = []
47
+
48
+ stopReason
49
+
50
+ /**
51
+ * Represents an API response object as {@link BedrockResponse} expects.
52
+ * It will be updated by the stream handler with information received
53
+ * during processing of the stream. Upon stream completion, this object
54
+ * will replace the `response` property of `passThroughParams`.
55
+ *
56
+ * @type {object}
57
+ */
58
+ response = {
59
+ response: {
60
+ headers: {},
61
+ statusCode: 200
62
+ },
63
+ output: {
64
+ output: {
65
+ message: {
66
+
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ /**
73
+ * The function that will be invoked once the stream processing has finished.
74
+ * It will receive `this.passThroughParams` as the only parameter.
75
+ */
76
+ onComplete
77
+
78
+ constructor({ stream, passThroughParams, onComplete }) {
79
+ this.passThroughParams = passThroughParams
80
+ this.stream = stream
81
+ this.onComplete = onComplete
82
+ this.segment = passThroughParams.segment
83
+ this.generator = handleConverse
84
+ }
85
+
86
+ /**
87
+ * Encodes the output body into a Uint8Array, updates the pass through
88
+ * parameters with the compiled response object, and invokes the response
89
+ * handler. The trace segment is also updated to mark the end of the stream
90
+ * processing and account for the time it took to process the stream within
91
+ * the trace.
92
+ */
93
+ finish() {
94
+ this.passThroughParams.response = this.response
95
+ this.onComplete(this.passThroughParams)
96
+
97
+ this.segment.touch()
98
+ }
99
+
100
+ /**
101
+ * If the given event is the last event in the stream, update the response
102
+ * headers with the metrics data from the event.
103
+ *
104
+ * @param {object} parsedEvent
105
+ */
106
+ updateHeaders(parsedEvent) {
107
+ this.response.response.headers = {
108
+ 'x-amzn-requestid': this.passThroughParams.response.response.headers['x-amzn-requestid']
109
+ }
110
+ delete parsedEvent['amazon-bedrock-invocationMetrics']
111
+ }
112
+ }
113
+
114
+ // eslint-disable-next-line sonarjs/cognitive-complexity
115
+ async function * handleConverse() {
116
+ let activeChunk = null
117
+
118
+ try {
119
+ for await (const event of this.stream) {
120
+ yield event
121
+ this.updateHeaders(event)
122
+ if (event.messageStart?.role) {
123
+ this.role = 'assistant'
124
+ } else if (event.contentBlockStart?.start) {
125
+ // Handles a Content block start event. Tool use only.
126
+ const blockStartData = event.contentBlockStart.start
127
+ if (blockStartData.toolUse) {
128
+ activeChunk = { toolUse: { name: blockStartData.toolUse.name } }
129
+ }
130
+ } else if (event.contentBlockStop) {
131
+ if (activeChunk !== null) {
132
+ this.observedChunks.push(activeChunk)
133
+ activeChunk = null
134
+ }
135
+ } else if (event.contentBlockDelta?.delta) {
136
+ if (event.contentBlockDelta.delta.text) {
137
+ // It seems like the first streamed chunk does not always start with a contentBlockStart message
138
+ // If the stream starts with a delta, assume the current chunk is text
139
+ if (activeChunk === null) {
140
+ activeChunk = { text: '' }
141
+ }
142
+ activeChunk.text += event.contentBlockDelta.delta.text
143
+ }
144
+ // There are also deltas for tool use (stringified inputs) but we don't currently record them so we just ignore for now
145
+ } else if (event.messageStop) {
146
+ this.stopReason = event.messageStop?.stopReason
147
+ }
148
+ }
149
+ } finally {
150
+ this.response.output.output.message.content = this.observedChunks
151
+ this.finish()
152
+ }
153
+ }
154
+
155
+ module.exports = ConverseStreamHandler
@@ -13,5 +13,6 @@ module.exports = {
13
13
  LlmEmbedding: require('./embedding'),
14
14
  LlmEvent: require('./event'),
15
15
  LlmError: require('./error'),
16
- StreamHandler: require('./stream-handler')
16
+ StreamHandler: require('./stream-handler'),
17
+ ConverseStreamHandler: require('./converse-stream-handler')
17
18
  }
@@ -31,6 +31,43 @@ function stringifyClaudeChunkedMessage(chunks) {
31
31
  return stringifiedChunks.join('\n\n')
32
32
  }
33
33
 
34
+ /**
35
+ *
36
+ * @param {object[]} chunks - The "chunks" that make up a single conceptual message. In a multi-modal scenario, a single message
37
+ * might have a number of different-typed chunks interspersed
38
+ * @returns {string} - A stringified version of the message. We make a best-effort effort attempt to represent non-text chunks. In the future
39
+ * we may want to extend the agent to support these non-text chunks in a richer way. Placeholders are represented in an XML-like format but
40
+ * are NOT intended to be parsed as valid XML
41
+ */
42
+ function stringifyConverseChunkedMessage(chunks) {
43
+ const stringifiedChunks = chunks.map((chunk) => {
44
+ if ('text' in chunk) {
45
+ return chunk.text
46
+ } else if ('image' in chunk) {
47
+ return '<image>'
48
+ } else if ('document' in chunk) {
49
+ return `<document>${chunk.document.name ?? ''}</document>`
50
+ } else if ('toolUse' in chunk) {
51
+ return `<tool_use>${chunk.toolUse?.name ?? ''}</tool_use>`
52
+ } else if ('json' in chunk) {
53
+ return `<json>${JSON.stringify(chunk.json)}</json>`
54
+ } else if ('toolResult' in chunk) {
55
+ // Tool results can have their own chunks. For now we intentionally don't recurse in for non-text tool results
56
+ return `<tool_result>${
57
+ stringifyConverseChunkedMessage(
58
+ (chunk.toolResult.content ?? [])
59
+ .filter(subChunk => !('toolUse' in subChunk || 'toolResult' in subChunk))
60
+ )
61
+ }</tool_result>`
62
+ } else if ('guardContent' in chunk) {
63
+ return `<guard_content>${chunk.guardContent?.text ?? ''}</guard_content>`
64
+ }
65
+ return '<unknown_chunk>'
66
+ })
67
+ return stringifiedChunks.join('\n\n')
68
+ }
69
+
34
70
  module.exports = {
35
- stringifyClaudeChunkedMessage
71
+ stringifyClaudeChunkedMessage,
72
+ stringifyConverseChunkedMessage
36
73
  }
package/lib/shim/shim.js CHANGED
@@ -2030,7 +2030,7 @@ function wrapStreamEmit({ stream, shim, segment, specEvent, shouldCreateSegment,
2030
2030
  const newContext = context.enterSegment({ segment: eventSegment })
2031
2031
  eventBoundEmit = shim.bindContext({ nodule: emit, full: true, context: newContext })
2032
2032
  }
2033
- eventSegment.addAttribute('count', ++emitCount)
2033
+ if (eventSegment) eventSegment.addAttribute('count', ++emitCount)
2034
2034
  emitToCall = eventBoundEmit
2035
2035
  }
2036
2036
  if (evnt === 'end' || evnt === 'error') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "12.24.0",
3
+ "version": "12.25.1",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -203,7 +203,7 @@
203
203
  "dependencies": {
204
204
  "@grpc/grpc-js": "^1.13.2",
205
205
  "@grpc/proto-loader": "^0.7.5",
206
- "@newrelic/security-agent": "^2.4.2",
206
+ "@newrelic/security-agent": "^2.4.4",
207
207
  "@opentelemetry/api": "^1.9.0",
208
208
  "@opentelemetry/core": "^2.0.0",
209
209
  "@opentelemetry/exporter-metrics-otlp-proto": "^0.201.1",