dd-trace 5.107.0 → 5.109.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/index.d.ts +22 -1
  2. package/package.json +6 -5
  3. package/packages/datadog-instrumentations/src/ai.js +43 -48
  4. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js-context-methods.js +18 -0
  5. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +111 -0
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
  7. package/packages/datadog-instrumentations/src/electron.js +1 -1
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/aws-durable-execution-sdk-js.js +31 -0
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  11. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  12. package/packages/datadog-instrumentations/src/ioredis.js +0 -1
  13. package/packages/datadog-instrumentations/src/iovalkey.js +1 -2
  14. package/packages/datadog-instrumentations/src/next.js +34 -0
  15. package/packages/datadog-instrumentations/src/openai.js +77 -18
  16. package/packages/datadog-instrumentations/src/redis.js +0 -1
  17. package/packages/datadog-instrumentations/src/vitest.js +60 -1
  18. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/checkpoint.js +31 -0
  19. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +55 -0
  20. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +114 -0
  21. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +128 -0
  22. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/index.js +19 -0
  23. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +224 -0
  24. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +43 -0
  25. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -7
  26. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +100 -37
  27. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +44 -27
  28. package/packages/datadog-plugin-bullmq/src/filter.js +35 -0
  29. package/packages/datadog-plugin-bullmq/src/producer.js +84 -4
  30. package/packages/datadog-plugin-fs/src/index.js +1 -0
  31. package/packages/datadog-plugin-redis/src/index.js +1 -2
  32. package/packages/datadog-plugin-vitest/src/index.js +4 -1
  33. package/packages/dd-trace/src/aiguard/channels.js +0 -1
  34. package/packages/dd-trace/src/aiguard/index.js +11 -49
  35. package/packages/dd-trace/src/aiguard/integrations/evaluate.js +46 -0
  36. package/packages/dd-trace/src/aiguard/integrations/openai.js +66 -0
  37. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +78 -0
  38. package/packages/{datadog-instrumentations/src/helpers/ai-messages.js → dd-trace/src/aiguard/messages/openai.js} +85 -193
  39. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +185 -0
  40. package/packages/dd-trace/src/appsec/channels.js +1 -0
  41. package/packages/dd-trace/src/appsec/downstream_requests.js +114 -60
  42. package/packages/dd-trace/src/appsec/iast/index.js +3 -2
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +54 -12
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +5 -1
  45. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +29 -4
  46. package/packages/dd-trace/src/appsec/rasp/ssrf.js +21 -12
  47. package/packages/dd-trace/src/appsec/reporter.js +1 -1
  48. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  49. package/packages/dd-trace/src/config/supported-configurations.json +31 -2
  50. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -0
  51. package/packages/dd-trace/src/dogstatsd.js +15 -8
  52. package/packages/dd-trace/src/exporters/agentless/index.js +7 -5
  53. package/packages/dd-trace/src/exporters/agentless/intake.js +43 -0
  54. package/packages/dd-trace/src/exporters/agentless/writer.js +5 -4
  55. package/packages/dd-trace/src/openfeature/flagging_provider.js +8 -1
  56. package/packages/dd-trace/src/plugins/ci_plugin.js +27 -2
  57. package/packages/dd-trace/src/plugins/index.js +3 -0
  58. package/packages/dd-trace/src/profiling/config.js +2 -0
  59. package/packages/dd-trace/src/profiling/profilers/events.js +26 -4
  60. package/packages/dd-trace/src/profiling/profilers/space.js +3 -1
  61. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  62. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  63. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  64. package/vendor/dist/protobufjs/index.js +1 -1
  65. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  66. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +0 -284
@@ -3,6 +3,9 @@
3
3
  const log = require('../../dd-trace/src/log')
4
4
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
5
5
  const { DsmPathwayCodec, getMessageSize } = require('../../dd-trace/src/datastreams')
6
+ const { getFilter } = require('./filter')
7
+
8
+ const filteredJobs = Symbol('bullmq.filteredJobs')
6
9
 
7
10
  // Customer-controlled metadata may be malformed JSON. Returning a fresh `{}`
8
11
  // on parse failure keeps the publish path alive instead of throwing into
@@ -31,6 +34,19 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
31
34
  }
32
35
 
33
36
  bindStart (ctx) {
37
+ let instrument = true
38
+ if (this.config.producerFilter) {
39
+ try {
40
+ instrument = this.shouldInstrument(ctx)
41
+ } catch (error) {
42
+ log.error('bullmq: producerFilter threw, filtering is disabled: %s', error.message)
43
+ }
44
+ }
45
+
46
+ if (!instrument) {
47
+ return { noop: true }
48
+ }
49
+
34
50
  const { resource, meta } = this.getSpanData(ctx)
35
51
  const span = this.startSpan({
36
52
  resource,
@@ -48,6 +64,17 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
48
64
  return ctx.currentStore
49
65
  }
50
66
 
67
+ configure (config) {
68
+ if (typeof config === 'boolean') {
69
+ return super.configure(config)
70
+ }
71
+ return super.configure({ ...config, producerFilter: getFilter(config) })
72
+ }
73
+
74
+ shouldInstrument (ctx) {
75
+ throw new Error('shouldInstrument must be implemented by subclass')
76
+ }
77
+
51
78
  getSpanData (ctx) {
52
79
  throw new Error('getSpanData must be implemented by subclass')
53
80
  }
@@ -87,6 +114,15 @@ class BaseBullmqProducerPlugin extends ProducerPlugin {
87
114
  class QueueAddPlugin extends BaseBullmqProducerPlugin {
88
115
  static prefix = 'tracing:orchestrion:bullmq:Queue_add'
89
116
 
117
+ shouldInstrument (ctx) {
118
+ return this.config.producerFilter({
119
+ name: ctx.arguments?.[0],
120
+ data: ctx.arguments?.[1],
121
+ opts: ctx.arguments?.[2],
122
+ queueName: ctx.self?.name,
123
+ })
124
+ }
125
+
90
126
  getSpanData (ctx) {
91
127
  const queueName = ctx.self?.name || 'bullmq'
92
128
  return {
@@ -128,6 +164,40 @@ class QueueAddPlugin extends BaseBullmqProducerPlugin {
128
164
  class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
129
165
  static prefix = 'tracing:orchestrion:bullmq:Queue_addBulk'
130
166
 
167
+ shouldInstrument (ctx) {
168
+ const jobs = this.#getFilteredJobs(ctx)
169
+ // Empty bulk calls are publish attempts and should keep producing a span.
170
+ return jobs === undefined || jobs.length > 0 || ctx.arguments?.[0].length === 0
171
+ }
172
+
173
+ #getFilteredJobs (ctx) {
174
+ if (Object.hasOwn(ctx, filteredJobs)) return ctx[filteredJobs]
175
+
176
+ const jobs = ctx.arguments?.[0]
177
+ if (!Array.isArray(jobs)) return
178
+
179
+ let allowedJobs
180
+ if (this.config.producerFilter) {
181
+ const queueName = ctx.self?.name
182
+ try {
183
+ allowedJobs = []
184
+ for (const job of jobs) {
185
+ if (job && this.config.producerFilter({ name: job.name, data: job.data, opts: job.opts, queueName })) {
186
+ allowedJobs.push(job)
187
+ }
188
+ }
189
+ } catch (error) {
190
+ log.error('bullmq: producerFilter threw, filtering is disabled: %s', error.message)
191
+ allowedJobs = jobs.filter(Boolean)
192
+ }
193
+ } else {
194
+ allowedJobs = jobs
195
+ }
196
+
197
+ ctx[filteredJobs] = allowedJobs
198
+ return allowedJobs
199
+ }
200
+
131
201
  operationName () {
132
202
  return 'bullmq.addBulk'
133
203
  }
@@ -139,14 +209,14 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
139
209
  resource: queueName,
140
210
  meta: {
141
211
  'messaging.destination.name': ctx.self?.name,
142
- 'messaging.batch.message_count': Array.isArray(jobs) ? jobs.length : undefined,
212
+ 'messaging.batch.message_count': jobs?.length,
143
213
  },
144
214
  }
145
215
  }
146
216
 
147
217
  injectTraceContext (span, ctx) {
148
- const jobs = ctx.arguments?.[0]
149
- if (!Array.isArray(jobs)) return
218
+ const jobs = this.#getFilteredJobs(ctx)
219
+ if (!jobs) return
150
220
 
151
221
  const cache = []
152
222
  for (let i = 0; i < jobs.length; i++) {
@@ -159,7 +229,7 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
159
229
  }
160
230
 
161
231
  setProducerCheckpoint (span, ctx) {
162
- const jobs = ctx.arguments?.[0] || []
232
+ const jobs = this.#getFilteredJobs(ctx) || []
163
233
  const queueName = ctx.self?.name || 'bullmq'
164
234
  const edgeTags = ['direction:out', `topic:${queueName}`, 'type:bullmq']
165
235
  const cache = ctx._ddMetadata
@@ -180,6 +250,16 @@ class QueueAddBulkPlugin extends BaseBullmqProducerPlugin {
180
250
  class FlowProducerAddPlugin extends BaseBullmqProducerPlugin {
181
251
  static prefix = 'tracing:orchestrion:bullmq:FlowProducer_add'
182
252
 
253
+ shouldInstrument (ctx) {
254
+ const flow = ctx.arguments?.[0]
255
+ return this.config.producerFilter({
256
+ name: flow?.name,
257
+ data: flow?.data,
258
+ opts: flow?.opts,
259
+ queueName: flow?.queueName,
260
+ })
261
+ }
262
+
183
263
  getSpanData (ctx) {
184
264
  const flow = ctx.arguments?.[0]
185
265
  const queueName = flow?.queueName || 'bullmq'
@@ -5,6 +5,7 @@ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
5
5
  class FsPlugin extends TracingPlugin {
6
6
  static id = 'fs'
7
7
  static operation = 'operation'
8
+ static experimental = true
8
9
 
9
10
  configure (...args) {
10
11
  return super.configure(...args)
@@ -28,7 +28,7 @@ class RedisPlugin extends CachePlugin {
28
28
  }
29
29
 
30
30
  bindStart (ctx) {
31
- const { db, command, args, argsStartIndex, connectionOptions, connectionName } = ctx
31
+ const { command, args, argsStartIndex, connectionOptions, connectionName } = ctx
32
32
 
33
33
  const resource = command
34
34
  const normalizedCommand = command.toUpperCase()
@@ -55,7 +55,6 @@ class RedisPlugin extends CachePlugin {
55
55
  type: this._spanType,
56
56
  meta: {
57
57
  'db.type': this._spanType,
58
- 'db.name': db || '0',
59
58
  [this.#rawCommandKey]: formatCommand(normalizedCommand, args, argsStartIndex),
60
59
  'out.host': connectionOptions.host,
61
60
  [CLIENT_PORT_KEY]: connectionOptions.port,
@@ -63,6 +63,8 @@ class VitestPlugin extends CiPlugin {
63
63
  testSessionId: testSessionSpanContext?.toTraceId(),
64
64
  testModuleId: testModuleSpanContext?.toSpanId(),
65
65
  testCommand: this.command,
66
+ repositoryRoot: this.repositoryRoot,
67
+ codeOwnersEntries: this.codeOwnersEntries,
66
68
  })
67
69
  })
68
70
 
@@ -307,10 +309,11 @@ class VitestPlugin extends CiPlugin {
307
309
  })
308
310
 
309
311
  this.addBind('ci:vitest:test-suite:start', (ctx) => {
310
- const { testSuiteAbsolutePath, frameworkVersion } = ctx
312
+ const { codeOwnersEntries, repositoryRoot, testSuiteAbsolutePath, frameworkVersion } = ctx
311
313
 
312
314
  const testCommand = ctx.testCommand || 'vitest run'
313
315
  const { testSessionId, testModuleId } = ctx
316
+ this._setRepositoryRoot(repositoryRoot, codeOwnersEntries)
314
317
  this.command = testCommand
315
318
  this.frameworkVersion = frameworkVersion
316
319
  const testSessionSpanContext = testSessionId && testModuleId
@@ -3,6 +3,5 @@
3
3
  const dc = require('dc-polyfill')
4
4
 
5
5
  module.exports = {
6
- aiguardChannel: dc.channel('dd-trace:ai:aiguard'),
7
6
  incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
8
7
  }
@@ -1,13 +1,16 @@
1
1
  'use strict'
2
2
 
3
3
  const log = require('../log')
4
- const { incomingHttpRequestStart, aiguardChannel } = require('./channels')
4
+ const { incomingHttpRequestStart } = require('./channels')
5
+ const openaiIntegration = require('./integrations/openai')
6
+ const vercelAiIntegration = require('./integrations/vercel-ai')
5
7
  const AIGuard = require('./sdk')
6
- const { SOURCE_AUTO, INTEGRATION_NONE } = require('./tags')
7
8
 
8
9
  let isEnabled = false
9
10
  let aiguard
10
11
  let block
12
+ let disableOpenAIIntegration
13
+ let disableVercelAiIntegration
11
14
 
12
15
  function onIncomingHttpRequestStart () {
13
16
  // No-op: subscribing ensures the HTTP plugin spreads req onto the store
@@ -21,7 +24,8 @@ function enable (tracer, config) {
21
24
  block = config.experimental?.aiguard?.block !== false
22
25
 
23
26
  incomingHttpRequestStart.subscribe(onIncomingHttpRequestStart)
24
- aiguardChannel.subscribe(onEvaluate)
27
+ disableOpenAIIntegration = openaiIntegration.enable(aiguard, block)
28
+ disableVercelAiIntegration = vercelAiIntegration.enable(aiguard, block)
25
29
 
26
30
  isEnabled = true
27
31
  } catch (err) {
@@ -34,56 +38,14 @@ function disable () {
34
38
  if (!isEnabled) return
35
39
 
36
40
  incomingHttpRequestStart.unsubscribe(onIncomingHttpRequestStart)
37
- aiguardChannel.unsubscribe(onEvaluate)
41
+ disableOpenAIIntegration?.()
42
+ disableVercelAiIntegration?.()
38
43
 
39
44
  aiguard = undefined
40
45
  isEnabled = false
41
46
  block = false
42
- }
43
-
44
- /**
45
- * Handles channel messages with pre-converted messages.
46
- *
47
- * @param {object} ctx
48
- * @param {Array<object>} ctx.messages
49
- * @param {string} [ctx.integration]
50
- * @param {object} [ctx.parentSpan] - LLM span to parent the `ai_guard` span under.
51
- * @param {AbortController} ctx.abortController
52
- * @param {Array<Promise<void>>} ctx.pending - Subscribers push only when they evaluate.
53
- */
54
- function onEvaluate (ctx) {
55
- // Decline to evaluate empty payloads by not pushing to pending.
56
- if (!ctx.messages?.length) {
57
- return
58
- }
59
-
60
- const opts = {
61
- block,
62
- source: SOURCE_AUTO,
63
- integration: ctx.integration || INTEGRATION_NONE,
64
- childOf: ctx.parentSpan,
65
- }
66
-
67
- try {
68
- ctx.pending.push(aiguard.evaluate(ctx.messages, opts).catch(handleEvaluationError.bind(null, ctx)))
69
- } catch (err) {
70
- ctx.pending.push(Promise.resolve().then(() => handleEvaluationError(ctx, err)))
71
- }
72
- }
73
-
74
- /**
75
- * Handles an AI Guard evaluation failure.
76
- *
77
- * @param {object} ctx
78
- * @param {AbortController} ctx.abortController
79
- * @param {Error} err
80
- */
81
- function handleEvaluationError (ctx, err) {
82
- if (err.name === 'AIGuardAbortError') {
83
- ctx.abortController.abort(err)
84
- } else {
85
- log.error('AIGuard: unexpected error during evaluation: %s', err.message)
86
- }
47
+ disableOpenAIIntegration = undefined
48
+ disableVercelAiIntegration = undefined
87
49
  }
88
50
 
89
51
  module.exports = { enable, disable }
@@ -0,0 +1,46 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+
5
+ /**
6
+ * Starts one AI Guard evaluation for a lifecycle ctx.
7
+ *
8
+ * Async evaluations are pushed synchronously during channel publication. If
9
+ * evaluation throws synchronously, the error is handled before publish returns.
10
+ *
11
+ * @param {object} ctx
12
+ * @param {AbortController} ctx.abortController
13
+ * @param {object} [ctx.parentSpan]
14
+ * @param {Array<Promise<void>>} ctx.pending
15
+ * @param {object} aiguard
16
+ * @param {Array<object>|undefined} messages
17
+ * @param {object} opts
18
+ */
19
+ function pushEvaluation (ctx, aiguard, messages, opts) {
20
+ if (!messages?.length) return
21
+
22
+ const evaluateOpts = ctx.parentSpan ? { ...opts, childOf: ctx.parentSpan } : opts
23
+
24
+ try {
25
+ ctx.pending.push(aiguard.evaluate(messages, evaluateOpts).catch(handleEvaluationError.bind(null, ctx)))
26
+ } catch (err) {
27
+ handleEvaluationError(ctx, err)
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Handles an AI Guard evaluation failure.
33
+ *
34
+ * @param {object} ctx
35
+ * @param {AbortController} ctx.abortController
36
+ * @param {Error} err
37
+ */
38
+ function handleEvaluationError (ctx, err) {
39
+ if (err.name === 'AIGuardAbortError') {
40
+ ctx.abortController.abort(err)
41
+ } else {
42
+ log.error('AIGuard: unexpected error during evaluation: %s', err.message)
43
+ }
44
+ }
45
+
46
+ module.exports = { pushEvaluation }
@@ -0,0 +1,66 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+
5
+ const {
6
+ getChatCompletionsInputMessages,
7
+ getChatCompletionsOutputMessages,
8
+ getResponsesInputMessages,
9
+ getResponsesOutputMessages,
10
+ } = require('../messages/openai')
11
+ const { SOURCE_AUTO } = require('../tags')
12
+ const { pushEvaluation } = require('./evaluate')
13
+
14
+ const chatCompletionsBeforeChannel = channel('dd-trace:openai:chat.completions:before')
15
+ const chatCompletionsAfterChannel = channel('dd-trace:openai:chat.completions:after')
16
+ const responsesBeforeChannel = channel('dd-trace:openai:responses:before')
17
+ const responsesAfterChannel = channel('dd-trace:openai:responses:after')
18
+
19
+ /**
20
+ * Subscribes AI Guard to OpenAI lifecycle channels.
21
+ *
22
+ * @param {object} aiguard
23
+ * @param {boolean} block
24
+ * @returns {() => void}
25
+ */
26
+ function enable (aiguard, block) {
27
+ const opts = { block, source: SOURCE_AUTO, integration: 'openai' }
28
+
29
+ function onChatCompletionsBefore (ctx) {
30
+ pushEvaluation(ctx, aiguard, getChatCompletionsInputMessages(ctx.args?.[0]), opts)
31
+ }
32
+
33
+ function onChatCompletionsAfter (ctx) {
34
+ const inputMessages = getChatCompletionsInputMessages(ctx.args?.[0])
35
+ if (!inputMessages?.length) return
36
+ for (const message of getChatCompletionsOutputMessages(ctx.body)) {
37
+ pushEvaluation(ctx, aiguard, [...inputMessages, message], opts)
38
+ }
39
+ }
40
+
41
+ function onResponsesBefore (ctx) {
42
+ pushEvaluation(ctx, aiguard, getResponsesInputMessages(ctx.args?.[0]), opts)
43
+ }
44
+
45
+ function onResponsesAfter (ctx) {
46
+ const inputMessages = getResponsesInputMessages(ctx.args?.[0])
47
+ if (!inputMessages?.length) return
48
+ const outputMessages = getResponsesOutputMessages(ctx.body)
49
+ if (!outputMessages.length) return
50
+ pushEvaluation(ctx, aiguard, [...inputMessages, ...outputMessages], opts)
51
+ }
52
+
53
+ chatCompletionsBeforeChannel.subscribe(onChatCompletionsBefore)
54
+ chatCompletionsAfterChannel.subscribe(onChatCompletionsAfter)
55
+ responsesBeforeChannel.subscribe(onResponsesBefore)
56
+ responsesAfterChannel.subscribe(onResponsesAfter)
57
+
58
+ return function disable () {
59
+ chatCompletionsBeforeChannel.unsubscribe(onChatCompletionsBefore)
60
+ chatCompletionsAfterChannel.unsubscribe(onChatCompletionsAfter)
61
+ responsesBeforeChannel.unsubscribe(onResponsesBefore)
62
+ responsesAfterChannel.unsubscribe(onResponsesAfter)
63
+ }
64
+ }
65
+
66
+ module.exports = { enable }
@@ -0,0 +1,78 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+
5
+ const { buildOutputMessages, convertVercelPromptToMessages } = require('../messages/vercel-ai')
6
+ const { SOURCE_AUTO } = require('../tags')
7
+ const { pushEvaluation } = require('./evaluate')
8
+
9
+ const doGenerateBeforeChannel = channel('dd-trace:vercel-ai:doGenerate:before')
10
+ const doGenerateAfterChannel = channel('dd-trace:vercel-ai:doGenerate:after')
11
+ const doStreamBeforeChannel = channel('dd-trace:vercel-ai:doStream:before')
12
+ const doStreamAfterChannel = channel('dd-trace:vercel-ai:doStream:after')
13
+
14
+ /**
15
+ * Subscribes AI Guard to Vercel AI lifecycle channels.
16
+ *
17
+ * @param {object} aiguard
18
+ * @param {boolean} block
19
+ * @returns {() => void}
20
+ */
21
+ function enable (aiguard, block) {
22
+ const opts = { block, source: SOURCE_AUTO, integration: 'ai' }
23
+
24
+ function onBefore (ctx) {
25
+ pushEvaluation(ctx, aiguard, convertVercelPromptToMessages(ctx.prompt), opts)
26
+ }
27
+
28
+ function onGenerateAfter (ctx) {
29
+ const inputMessages = convertVercelPromptToMessages(ctx.prompt)
30
+ if (!inputMessages.length || !ctx.result?.content?.length) return
31
+
32
+ pushEvaluation(ctx, aiguard, buildOutputMessages(inputMessages, ctx.result.content), opts)
33
+ }
34
+
35
+ function onStreamAfter (ctx) {
36
+ const inputMessages = convertVercelPromptToMessages(ctx.prompt)
37
+ if (!inputMessages.length || !ctx.chunks?.length) return
38
+
39
+ pushEvaluation(ctx, aiguard, buildOutputMessages(inputMessages, getStreamContent(ctx.chunks)), opts)
40
+ }
41
+
42
+ doGenerateBeforeChannel.subscribe(onBefore)
43
+ doGenerateAfterChannel.subscribe(onGenerateAfter)
44
+ doStreamBeforeChannel.subscribe(onBefore)
45
+ doStreamAfterChannel.subscribe(onStreamAfter)
46
+
47
+ return function disable () {
48
+ doGenerateBeforeChannel.unsubscribe(onBefore)
49
+ doGenerateAfterChannel.unsubscribe(onGenerateAfter)
50
+ doStreamBeforeChannel.unsubscribe(onBefore)
51
+ doStreamAfterChannel.unsubscribe(onStreamAfter)
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Converts Vercel stream chunks into the content shape used by doGenerate results.
57
+ *
58
+ * @param {Array<object>} chunks
59
+ * @returns {Array<object>}
60
+ */
61
+ function getStreamContent (chunks) {
62
+ const toolCalls = []
63
+ const textParts = []
64
+
65
+ for (const chunk of chunks) {
66
+ if (chunk?.type === 'tool-call') {
67
+ toolCalls.push(chunk)
68
+ } else if (chunk?.type === 'text-delta') {
69
+ textParts.push(chunk.textDelta)
70
+ }
71
+ }
72
+
73
+ if (toolCalls.length) return toolCalls
74
+ const text = textParts.join('')
75
+ return text ? [{ type: 'text', text }] : []
76
+ }
77
+
78
+ module.exports = { enable }