newrelic 13.9.0 → 13.9.2

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,32 @@
1
+ ### v13.9.2 (2026-01-14)
2
+
3
+ #### Bug fixes
4
+
5
+ * Updated message consumer subscribers to properly time the consumption actions ([#3660](https://github.com/newrelic/node-newrelic/pull/3660)) ([ef1b611](https://github.com/newrelic/node-newrelic/commit/ef1b611a7c3b45f5fc8d483661859c1595bbe4ab))
6
+
7
+ #### Documentation
8
+
9
+ * Updated compatibility report ([#3654](https://github.com/newrelic/node-newrelic/pull/3654)) ([ca16dae](https://github.com/newrelic/node-newrelic/commit/ca16daefe4da1c17253e165c68d198d607c72d0b))
10
+
11
+ #### Miscellaneous chores
12
+
13
+ * Update OTEL metrics test ([#3661](https://github.com/newrelic/node-newrelic/pull/3661)) ([ae84b62](https://github.com/newrelic/node-newrelic/commit/ae84b62a3c7ef753d4ace8836f38405b4cbbfd88))
14
+
15
+ #### Tests
16
+
17
+ * Added cross agent tests for sampler configuration and sampling rate scenarios ([#3648](https://github.com/newrelic/node-newrelic/pull/3648)) ([fa0e2d1](https://github.com/newrelic/node-newrelic/commit/fa0e2d1bd78051c7f716ebc65bd7f3842698103e))
18
+
19
+ ### v13.9.1 (2026-01-12)
20
+
21
+ #### Bug fixes
22
+
23
+ * Updated `getHostnameSafe` to invalidate the cache is trying to assign the host based on the gcp cloud run id ([#3650](https://github.com/newrelic/node-newrelic/pull/3650)) ([d395c76](https://github.com/newrelic/node-newrelic/commit/d395c76d5612a7e562efa1a807c35e805a38ab14))
24
+ * Updated logic to properly assign content and role in LangChain chat completion messages ([#3638](https://github.com/newrelic/node-newrelic/pull/3638)) ([407bcb6](https://github.com/newrelic/node-newrelic/commit/407bcb66b7e3be1b6f1d28bcf6e3b022a7022c96))
25
+
26
+ #### Documentation
27
+
28
+ * Updated compatibility report ([#3643](https://github.com/newrelic/node-newrelic/pull/3643)) ([8929ab3](https://github.com/newrelic/node-newrelic/commit/8929ab3ae6cc28edbef4bee41a2084154361d6e0))
29
+
1
30
  ### v13.9.0 (2026-01-08)
2
31
 
3
32
  #### Features
@@ -8203,3 +8232,4 @@ Special thanks to Ryan Copley (@RyanCopley) for the contribution.
8203
8232
 
8204
8233
 
8205
8234
 
8235
+
@@ -993,7 +993,21 @@ Config.prototype.getIPAddresses = function getIPAddresses() {
993
993
  function getHostnameSafe(gcpId = null) {
994
994
  let _hostname
995
995
  const config = this
996
- this.getHostnameSafe = function getCachedHostname() {
996
+ /**
997
+ * We cache the result of _hostname unless an argument is passed in.
998
+ * The argument of `gcpId` is only passed in during the collection of
999
+ * facts: see `lib/collector/facts.js`. This needs to take precedence
1000
+ * over whatever the hostname was previously because gcp cloud run instances
1001
+ * report as localhost and we want to use the cloud run id
1002
+ *
1003
+ * @param {string} gcpId GCP metadata ID
1004
+ * @returns {string} host name
1005
+ */
1006
+ this.getHostnameSafe = function getCachedHostname(gcpId = null) {
1007
+ if (gcpId) {
1008
+ return getHostnameSafe.call(config, gcpId)
1009
+ }
1010
+
997
1011
  return _hostname
998
1012
  }
999
1013
  try {
@@ -22,6 +22,7 @@ const { makeId } = require('../../util/hashes')
22
22
  */
23
23
  const defaultParams = {
24
24
  content: '',
25
+ role: null,
25
26
  sequence: 0,
26
27
  completionId: makeId(36),
27
28
  isResponse: undefined
@@ -29,6 +30,7 @@ const defaultParams = {
29
30
 
30
31
  class LangChainCompletionMessage extends LangChainEvent {
31
32
  content
33
+ role
32
34
  sequence
33
35
  completion_id
34
36
  is_response
@@ -47,6 +49,13 @@ class LangChainCompletionMessage extends LangChainEvent {
47
49
  this.sequence = params.sequence
48
50
  this.completion_id = params.completionId
49
51
  this.is_response = params.isResponse ?? false
52
+ if (params.role) {
53
+ this.role = params.role
54
+ } else {
55
+ // As a backup, we can infer the role based on if it
56
+ // is a response or not.
57
+ this.role = this.is_response ? 'assistant' : 'user'
58
+ }
50
59
 
51
60
  if (agent.config.ai_monitoring.record_content.enabled === true) {
52
61
  this.content = params.content
@@ -46,7 +46,7 @@ class LangchainSubscriber extends Subscriber {
46
46
  events,
47
47
  metadata,
48
48
  tags,
49
- err,
49
+ err
50
50
  }) {
51
51
  const { agent } = this
52
52
  segment.end()
@@ -74,7 +74,8 @@ class LangchainSubscriber extends Subscriber {
74
74
  events,
75
75
  completionSummary,
76
76
  segment,
77
- transaction
77
+ transaction,
78
+ pkgVersion
78
79
  })
79
80
 
80
81
  if (err) {
@@ -100,8 +101,9 @@ class LangchainSubscriber extends Subscriber {
100
101
  * @param {LangChainCompletionSummary} params.completionSummary LlmChatCompletionSummary event
101
102
  * @param {TraceSegment} params.segment active segment
102
103
  * @param {Transaction} params.transaction active transaction
104
+ * @param {string} params.pkgVersion module version of the LangChain package
103
105
  */
104
- recordCompletions({ events, completionSummary, segment, transaction }) {
106
+ recordCompletions({ events, completionSummary, segment, transaction, pkgVersion }) {
105
107
  const { agent, logger } = this
106
108
  for (let i = 0; i < events.length; i += 1) {
107
109
  let msg = events[i]
@@ -121,6 +123,7 @@ class LangchainSubscriber extends Subscriber {
121
123
  sequence: i,
122
124
  agent,
123
125
  content: msgString,
126
+ role: msg?.role,
124
127
  completionId: completionSummary.id,
125
128
  segment,
126
129
  transaction,
@@ -131,7 +134,7 @@ class LangchainSubscriber extends Subscriber {
131
134
 
132
135
  this.recordEvent({
133
136
  type: 'LlmChatCompletionMessage',
134
- pkgVersion: this.moduleVersion,
137
+ pkgVersion,
135
138
  msg: completionMsg
136
139
  })
137
140
  }
@@ -37,7 +37,7 @@ class LangchainRunnableStreamSubscriber extends LangchainSubscriber {
37
37
 
38
38
  asyncEnd(data) {
39
39
  const { agent, logger } = this
40
- // Check ai_monitoring config
40
+ // Exit early if need be.
41
41
  if (!this.enabled) {
42
42
  if (!this.streamingEnabled) {
43
43
  logger.warn('Langchain streaming instrumentation is disabled, stream will not be instrumented.')
@@ -48,26 +48,26 @@ class LangchainRunnableStreamSubscriber extends LangchainSubscriber {
48
48
  .incrementCallCount()
49
49
  return
50
50
  }
51
-
52
- // Instrument stream
53
51
  const ctx = this.agent.tracer.getContext()
54
52
  const { segment, transaction } = ctx
55
53
  if (transaction?.isActive() !== true) {
56
54
  return
57
55
  }
58
56
 
57
+ // Extract data.
59
58
  const request = data?.arguments?.[0]
60
59
  const params = data?.arguments?.[1] || {}
61
60
  const metadata = params?.metadata ?? {}
62
61
  const tags = params?.tags ?? []
63
62
  const { result: output, error: err, moduleVersion: pkgVersion } = data
64
63
 
65
- // Input error occurred which means a stream was not created.
66
- // Skip instrumenting streaming and create Llm Events from
67
- // the data we have
64
+ // Instrument stream.
68
65
  if (output?.next) {
69
66
  this.wrapNextHandler({ output, segment, request, metadata, tags, transaction, pkgVersion })
70
67
  } else {
68
+ // Input error occurred which means a stream was not created.
69
+ // Skip instrumenting streaming and create Llm Events from
70
+ // the data we have
71
71
  this.recordChatCompletionEvents({
72
72
  transaction,
73
73
  segment,
@@ -115,11 +115,16 @@ class LangchainRunnableStreamSubscriber extends LangchainSubscriber {
115
115
  pkgVersion
116
116
  })
117
117
  } else {
118
+ // Concat the streamed content
118
119
  if (typeof result?.value?.content === 'string') {
119
- // if result.value is a AIMessageChunk
120
+ // LangChain BaseMessageChunk case
120
121
  content += result.value.content
121
- } else {
122
+ } else if (typeof result?.value === 'string') {
123
+ // Base LangChain case
122
124
  content += result.value
125
+ } else if (typeof result?.value?.[0] === 'string') {
126
+ // Array parser case
127
+ content += result.value[0]
123
128
  }
124
129
  }
125
130
  return result
@@ -105,7 +105,7 @@ class MessageConsumerSubscriber extends Subscriber {
105
105
  */
106
106
  asyncStart() {
107
107
  const ctx = this.agent.tracer.getContext()
108
- const tx = ctx?.transaction
108
+ const tx = ctx.transaction
109
109
  tx.setPartialName(this.name)
110
110
  // Note: this is not using `Subscriber.createSegment`
111
111
  // this is because it enters the segment and returns a new ctx
@@ -117,6 +117,7 @@ class MessageConsumerSubscriber extends Subscriber {
117
117
  parent: tx.trace.root,
118
118
  transaction: tx
119
119
  })
120
+ tx.baseSegment.start()
120
121
 
121
122
  this.addConsumeParameters(tx)
122
123
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "13.9.0",
3
+ "version": "13.9.2",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -198,6 +198,7 @@
198
198
  "imports": {
199
199
  "#agentlib/*.js": "./lib/*.js",
200
200
  "#testlib/*.js": "./test/lib/*.js",
201
+ "#testlib/*.json": "./test/lib/*.json",
201
202
  "#test/assert": "./test/lib/custom-assertions/index.js"
202
203
  },
203
204
  "dependencies": {