dd-trace 5.108.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 (58) hide show
  1. package/index.d.ts +22 -1
  2. package/package.json +2 -1
  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 +111 -58
  42. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +54 -12
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +5 -1
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +29 -4
  45. package/packages/dd-trace/src/appsec/rasp/ssrf.js +19 -11
  46. package/packages/dd-trace/src/config/generated-config-types.d.ts +3 -0
  47. package/packages/dd-trace/src/config/supported-configurations.json +24 -2
  48. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -0
  49. package/packages/dd-trace/src/dogstatsd.js +15 -8
  50. package/packages/dd-trace/src/exporters/agentless/index.js +7 -5
  51. package/packages/dd-trace/src/exporters/agentless/intake.js +43 -0
  52. package/packages/dd-trace/src/exporters/agentless/writer.js +5 -4
  53. package/packages/dd-trace/src/openfeature/flagging_provider.js +8 -1
  54. package/packages/dd-trace/src/plugins/ci_plugin.js +27 -2
  55. package/packages/dd-trace/src/plugins/index.js +3 -0
  56. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  57. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  58. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +0 -284
@@ -7,6 +7,7 @@ const tracerVersion = require('../../../../../package.json').version
7
7
 
8
8
  const BaseWriter = require('../common/writer')
9
9
  const { AgentlessJSONEncoder } = require('../../encode/agentless-json')
10
+ const { computeIntakeUrl, INTAKE_PATH } = require('./intake')
10
11
 
11
12
  /**
12
13
  * Writer for agentless APM trace intake.
@@ -28,7 +29,7 @@ class AgentlessWriter extends BaseWriter {
28
29
 
29
30
  if (!url) {
30
31
  try {
31
- this._url = new URL(`https://public-trace-http-intake.logs.${site}`)
32
+ this._url = new URL(computeIntakeUrl(site))
32
33
  } catch (err) {
33
34
  log.error(
34
35
  'Invalid site value for agentless intake: %s. Cannot construct URL. Error: %s',
@@ -121,7 +122,7 @@ class AgentlessWriter extends BaseWriter {
121
122
  this.#apiKeyMissing = false
122
123
 
123
124
  const options = {
124
- path: '/v1/input',
125
+ path: INTAKE_PATH,
125
126
  method: 'POST',
126
127
  headers: {
127
128
  'Content-Type': 'application/json',
@@ -140,7 +141,7 @@ class AgentlessWriter extends BaseWriter {
140
141
 
141
142
  request(data, options, (err, res, statusCode) => {
142
143
  if (err) {
143
- this._logRequestError(err, statusCode, count)
144
+ this.#logRequestError(err, statusCode, count)
144
145
  done()
145
146
  return
146
147
  }
@@ -156,7 +157,7 @@ class AgentlessWriter extends BaseWriter {
156
157
  * @param {number} statusCode - HTTP status code (if available)
157
158
  * @param {number} count - Number of traces that were being sent
158
159
  */
159
- _logRequestError (err, statusCode, count) {
160
+ #logRequestError (err, statusCode, count) {
160
161
  if (statusCode === 401 || statusCode === 403) {
161
162
  log.error(
162
163
  'Authentication failed sending %d trace(s) (status %s). Verify DD_API_KEY is valid.',
@@ -1,12 +1,19 @@
1
1
  'use strict'
2
2
 
3
- const { DatadogNodeServerProvider } = require('@datadog/openfeature-node-server')
4
3
  const { channel } = require('dc-polyfill')
5
4
  const log = require('../log')
6
5
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
7
6
  const EvalMetricsHook = require('./eval-metrics-hook')
8
7
  const SpanEnrichmentHook = require('./span-enrichment-hook')
9
8
 
9
+ // Bundler-opaque require for the optional peer chain
10
+ // `@datadog/openfeature-node-server` -> `@openfeature/server-sdk` ->
11
+ // `@openfeature/core`. Same shape as `helpers/rewriter/compiler.js`.
12
+ // Refs: https://github.com/DataDog/dd-trace-js/issues/8635
13
+ // eslint-disable-next-line camelcase, no-undef
14
+ const runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
15
+ const { DatadogNodeServerProvider } = runtimeRequire(['@datadog/openfeature', 'node', 'server'].join('-'))
16
+
10
17
  /**
11
18
  * OpenFeature provider that integrates with Datadog's feature flagging system.
12
19
  * Extends DatadogNodeServerProvider to add tracer integration and configuration management.
@@ -442,6 +442,28 @@ module.exports = class CiPlugin extends Plugin {
442
442
  }
443
443
  }
444
444
 
445
+ /**
446
+ * Updates repository-root-dependent state when a worker receives the root from
447
+ * the coordinator process after plugin configuration.
448
+ *
449
+ * @param {string|undefined} repositoryRoot - Repository root discovered by the coordinator process.
450
+ * @param {Array<{ pattern: string, owners: string[] }>|null|undefined} codeOwnersEntries
451
+ * Parsed CODEOWNERS entries discovered by the coordinator process.
452
+ * @returns {void}
453
+ */
454
+ _setRepositoryRoot (repositoryRoot, codeOwnersEntries) {
455
+ if (codeOwnersEntries !== undefined) {
456
+ this.codeOwnersEntries = codeOwnersEntries
457
+ }
458
+
459
+ if (!repositoryRoot || repositoryRoot === this.repositoryRoot) return
460
+
461
+ this.repositoryRoot = repositoryRoot
462
+ if (codeOwnersEntries === undefined) {
463
+ this.codeOwnersEntries = getCodeOwnersFileEntries(this.repositoryRoot)
464
+ }
465
+ }
466
+
445
467
  /**
446
468
  * Returns request error tags from the test session span for propagation to module, suite and test spans.
447
469
  * @returns {Record<string, string>}
@@ -610,6 +632,8 @@ module.exports = class CiPlugin extends Plugin {
610
632
  const workerTestFramework = WORKER_EXPORTER_TO_TEST_FRAMEWORK[exporter]
611
633
  this.shouldSkipGitMetadataExtraction = workerTestFramework &&
612
634
  TEST_FRAMEWORKS_TO_SKIP_GIT_METADATA_EXTRACTION.has(workerTestFramework)
635
+ const shouldDeferCodeOwnersEntries = workerTestFramework === 'vitest'
636
+ const shouldDeferRepositoryRoot = workerTestFramework === 'vitest'
613
637
 
614
638
  this.testEnvironmentMetadata = getTestEnvironmentMetadata(
615
639
  this.constructor.id,
@@ -635,9 +659,10 @@ module.exports = class CiPlugin extends Plugin {
635
659
  [GIT_COMMIT_HEAD_MESSAGE]: commitHeadMessage,
636
660
  } = this.testEnvironmentMetadata
637
661
 
638
- this.repositoryRoot = repositoryRoot || getRepositoryRoot() || process.cwd()
662
+ this.repositoryRoot = repositoryRoot ||
663
+ (shouldDeferRepositoryRoot ? process.cwd() : getRepositoryRoot() || process.cwd())
639
664
 
640
- this.codeOwnersEntries = getCodeOwnersFileEntries(this.repositoryRoot)
665
+ this.codeOwnersEntries = shouldDeferCodeOwnersEntries ? null : getCodeOwnersFileEntries(this.repositoryRoot)
641
666
 
642
667
  this.ciProviderName = ciProviderName
643
668
 
@@ -8,6 +8,7 @@ const plugins = {
8
8
  get '@azure/event-hubs' () { return require('../../../datadog-plugin-azure-event-hubs/src') },
9
9
  get '@azure/functions' () { return require('../../../datadog-plugin-azure-functions/src') },
10
10
  get '@modelcontextprotocol/sdk' () { return require('../../../datadog-plugin-modelcontextprotocol-sdk/src') },
11
+ get '@aws/durable-execution-sdk-js' () { return require('../../../datadog-plugin-aws-durable-execution-sdk-js/src') },
11
12
  get 'durable-functions' () { return require('../../../datadog-plugin-azure-durable-functions/src') },
12
13
  get '@azure/service-bus' () { return require('../../../datadog-plugin-azure-service-bus/src') },
13
14
  get '@cucumber/cucumber' () { return require('../../../datadog-plugin-cucumber/src') },
@@ -55,6 +56,7 @@ const plugins = {
55
56
  get express () { return require('../../../datadog-plugin-express/src') },
56
57
  get fastify () { return require('../../../datadog-plugin-fastify/src') },
57
58
  get 'find-my-way' () { return require('../../../datadog-plugin-find-my-way/src') },
59
+ get fs () { return require('../../../datadog-plugin-fs/src') },
58
60
  get 'global:fetch' () { return require('../../../datadog-plugin-fetch/src') },
59
61
  get graphql () { return require('../../../datadog-plugin-graphql/src') },
60
62
  get grpc () { return require('../../../datadog-plugin-grpc/src') },
@@ -97,6 +99,7 @@ const plugins = {
97
99
  get net () { return require('../../../datadog-plugin-net/src') },
98
100
  get next () { return require('../../../datadog-plugin-next/src') },
99
101
  get 'node:dns' () { return require('../../../datadog-plugin-dns/src') },
102
+ get 'node:fs' () { return require('../../../datadog-plugin-fs/src') },
100
103
  get 'node:http' () { return require('../../../datadog-plugin-http/src') },
101
104
  get 'node:http2' () { return require('../../../datadog-plugin-http2/src') },
102
105
  get 'node:https' () { return require('../../../datadog-plugin-http/src') },
@@ -13,6 +13,18 @@ const serverless = {
13
13
  serviceName: identityService,
14
14
  },
15
15
  },
16
+ client: {
17
+ 'aws-durable-execution-sdk-js': {
18
+ opName: () => 'aws.durable.invoke',
19
+ serviceName: identityService,
20
+ },
21
+ },
22
+ internal: {
23
+ 'aws-durable-execution-sdk-js': {
24
+ opName: () => 'aws.durable.execute',
25
+ serviceName: identityService,
26
+ },
27
+ },
16
28
  }
17
29
 
18
30
  module.exports = serverless
@@ -13,6 +13,18 @@ const serverless = {
13
13
  serviceName: identityService,
14
14
  },
15
15
  },
16
+ client: {
17
+ 'aws-durable-execution-sdk-js': {
18
+ opName: () => 'aws.durable.invoke',
19
+ serviceName: identityService,
20
+ },
21
+ },
22
+ internal: {
23
+ 'aws-durable-execution-sdk-js': {
24
+ opName: () => 'aws.durable.execute',
25
+ serviceName: identityService,
26
+ },
27
+ },
16
28
  }
17
29
 
18
30
  module.exports = serverless
@@ -1,284 +0,0 @@
1
- 'use strict'
2
-
3
- const dc = require('dc-polyfill')
4
- const shimmer = require('../../../datadog-shimmer')
5
- const {
6
- convertOpenAIResponseItemsToMessages,
7
- convertOpenAIResponsePromptToMessages,
8
- normalizeOpenAIChatMessages,
9
- } = require('./ai-messages')
10
-
11
- // TODO: this channel name is incorrect, instrumentations publish with THEIR name, not with their subscribers names.
12
- const aiguardChannel = dc.channel('dd-trace:ai:aiguard')
13
-
14
- /**
15
- * @typedef {object} ResourceHandler
16
- * @property {(callArgs: object) => (Array<object>|undefined)} getInputMessages
17
- * @property {(body: object) => Array<object>} getOutputMessages
18
- * @property {(inputMessages: Array<object>, outputMessages: Array<object>, parentSpan?: object)
19
- * => Promise<unknown>} publishOutputEvaluation
20
- */
21
-
22
- /**
23
- * @typedef {object} Guard
24
- * @property {ResourceHandler} handler
25
- * @property {Array<object>} inputMessages
26
- * @property {() => Promise<void>} getInputEval
27
- * @property {object} [parentSpan] - LLM span (`openai.request`) to nest `ai_guard` spans under.
28
- * Set by the instrumentation once the LLM span is active.
29
- */
30
-
31
- /**
32
- * Publishes already-converted AI-style messages to the AI Guard evaluation channel.
33
- *
34
- * Subscribers push async work into `pending` and abort `abortController` to block.
35
- *
36
- * @param {Array<object>} messages - AI-style messages to evaluate.
37
- * @param {object} [parentSpan] - LLM span to use as the `ai_guard` span's parent.
38
- * @returns {Promise<void>}
39
- */
40
- function publishEvaluation (messages, parentSpan) {
41
- const abortController = new AbortController()
42
- const ctx = { messages, integration: 'openai', parentSpan, abortController, pending: [] }
43
-
44
- aiguardChannel.publish(ctx)
45
-
46
- return Promise.all(ctx.pending).then(() => {
47
- if (abortController.signal.aborted) {
48
- throw abortController.signal.reason
49
- }
50
- })
51
- }
52
-
53
- /**
54
- * Extracts OpenAI input messages from a `chat.completions.create` call.
55
- *
56
- * @param {object} callArgs - First argument passed to the wrapped method
57
- * @returns {Array<object>|undefined}
58
- */
59
- function getChatCompletionsInputMessages (callArgs) {
60
- return normalizeOpenAIChatMessages(callArgs?.messages)
61
- }
62
-
63
- /**
64
- * Extracts OpenAI output messages from a `chat.completions.create` parsed body.
65
- * Includes any choice whose message carries content (including empty string),
66
- * `tool_calls`, a `refusal` field, or the deprecated `function_call` field. GPT-4o
67
- * emits `{content: null, refusal: "..."}` on policy refusals, and pre-tool-call
68
- * SDK paths still produce `function_call`-only output — AI Guard must still see them.
69
- *
70
- * @param {object} body - Parsed response body
71
- * @returns {Array<object>}
72
- */
73
- function getChatCompletionsOutputMessages (body) {
74
- const eligible = []
75
- const choices = Array.isArray(body?.choices) ? body.choices : []
76
- for (const choice of choices) {
77
- const message = choice?.message
78
- if (
79
- message?.content != null ||
80
- message?.tool_calls?.length ||
81
- message?.refusal != null ||
82
- message?.function_call != null
83
- ) {
84
- eligible.push(message)
85
- }
86
- }
87
- return normalizeOpenAIChatMessages(eligible) ?? []
88
- }
89
-
90
- /**
91
- * Publishes AI Guard After Model evaluation for `chat.completions` output.
92
- *
93
- * Chat completions may return multiple choices when `n > 1`. Screen every choice
94
- * concurrently so any unsafe assistant output rejects `.parse()`, regardless of
95
- * which choice the caller ends up using.
96
- *
97
- * @param {Array<object>} inputMessages
98
- * @param {Array<object>} outputMessages - One entry per choice
99
- * @param {object} [parentSpan]
100
- * @returns {Promise<Array<void>>}
101
- */
102
- function publishChatCompletionsOutputEvaluation (inputMessages, outputMessages, parentSpan) {
103
- const evals = []
104
- for (const message of outputMessages) {
105
- evals.push(publishEvaluation([...inputMessages, message], parentSpan))
106
- }
107
- return Promise.all(evals)
108
- }
109
-
110
- /**
111
- * Extracts OpenAI input messages from a `responses.create` call. The `instructions`
112
- * field is treated as a developer prompt — it directly steers model behavior and the
113
- * LLMObs OpenAI plugin already surfaces it as one — so AI Guard must screen it too.
114
- *
115
- * AI Guard `/evaluate` accepts a single leading system/developer message; if the
116
- * caller's `input` already begins with one, prepend the `instructions` text to its
117
- * content rather than emit a second developer turn.
118
- *
119
- * @param {object} callArgs - First argument passed to the wrapped method
120
- * @returns {Array<object>|undefined}
121
- */
122
- function getResponsesInputMessages (callArgs) {
123
- const messages = [
124
- ...convertOpenAIResponseItemsToMessages(callArgs?.input, 'user'),
125
- ...convertOpenAIResponsePromptToMessages(callArgs?.prompt),
126
- ]
127
-
128
- const instructions = typeof callArgs?.instructions === 'string' && callArgs.instructions.length
129
- ? callArgs.instructions
130
- : null
131
- if (!instructions) return messages.length ? messages : undefined
132
-
133
- const first = messages[0]
134
- if (first && (first.role === 'developer' || first.role === 'system')) {
135
- const merged = { role: 'developer', content: mergeInstructionsWithContent(instructions, first.content) }
136
- return [merged, ...messages.slice(1)]
137
- }
138
- return [{ role: 'developer', content: instructions }, ...messages]
139
- }
140
-
141
- /**
142
- * Merges Responses API instructions with an existing leading developer/system content value.
143
- *
144
- * @param {string} instructions
145
- * @param {string|Array<object>|undefined} content
146
- * @returns {string|Array<object>}
147
- */
148
- function mergeInstructionsWithContent (instructions, content) {
149
- if (Array.isArray(content)) return [{ type: 'text', text: instructions }, ...content]
150
- if (typeof content === 'string' && content.length) return `${instructions}\n\n${content}`
151
- return instructions
152
- }
153
-
154
- /**
155
- * Extracts OpenAI output messages from a `responses.create` parsed body.
156
- *
157
- * @param {object} body - Parsed response body
158
- * @returns {Array<object>}
159
- */
160
- function getResponsesOutputMessages (body) {
161
- return convertOpenAIResponseItemsToMessages(body?.output, 'assistant')
162
- }
163
-
164
- /**
165
- * Publishes AI Guard After Model evaluation for `responses` output.
166
- *
167
- * The Responses API returns a single conversation turn whose `output` items form one
168
- * coherent message (reasoning steps + final assistant message + tool calls + ...);
169
- * they are screened together as a single evaluation.
170
- *
171
- * @param {Array<object>} inputMessages
172
- * @param {Array<object>} outputMessages
173
- * @param {object} [parentSpan]
174
- * @returns {Promise<void>}
175
- */
176
- function publishResponsesOutputEvaluation (inputMessages, outputMessages, parentSpan) {
177
- return publishEvaluation([...inputMessages, ...outputMessages], parentSpan)
178
- }
179
-
180
- /**
181
- * Per-resource handlers describing how AI Guard reads inputs and screens outputs for
182
- * each LLM-prompt-accepting OpenAI endpoint. The keys also serve as the set of
183
- * resources eligible for AI Guard evaluation.
184
- *
185
- * @type {Record<string, ResourceHandler>}
186
- */
187
- const RESOURCE_HANDLERS = {
188
- 'chat.completions': {
189
- getInputMessages: getChatCompletionsInputMessages,
190
- getOutputMessages: getChatCompletionsOutputMessages,
191
- publishOutputEvaluation: publishChatCompletionsOutputEvaluation,
192
- },
193
- responses: {
194
- getInputMessages: getResponsesInputMessages,
195
- getOutputMessages: getResponsesOutputMessages,
196
- publishOutputEvaluation: publishResponsesOutputEvaluation,
197
- },
198
- }
199
-
200
- /**
201
- * Reports whether the AI Guard channel has subscribers. The OpenAI instrumentation
202
- * uses this to decide whether to take the AI Guard path at all.
203
- *
204
- * @returns {boolean}
205
- */
206
- function hasSubscribers () {
207
- return aiguardChannel.hasSubscribers
208
- }
209
-
210
- /**
211
- * Builds a guard handle when AI Guard is enabled and applicable to this call. The
212
- * handle binds the per-resource handler so downstream functions never re-dispatch
213
- * on `baseResource`. Returns null when AI Guard does not apply (no subscribers,
214
- * non-eligible resource, streaming, or no input messages).
215
- *
216
- * @param {string} baseResource - e.g. `'chat.completions'` or `'responses'`
217
- * @param {object} callArgs - First argument passed to the wrapped OpenAI method
218
- * @param {boolean} stream - Whether the caller asked for a streamed response
219
- * @returns {Guard|null}
220
- */
221
- function createGuard (baseResource, callArgs, stream) {
222
- // Streaming AI Guard support lands in a follow-up PR. For now, provider-level AI
223
- // Guard only evaluates non-streaming responses.
224
- if (stream || !aiguardChannel.hasSubscribers) return null
225
- const handler = RESOURCE_HANDLERS[baseResource]
226
- if (!handler) return null
227
-
228
- const inputMessages = handler.getInputMessages(callArgs)
229
- if (!inputMessages) return null
230
-
231
- let inputEvalPromise
232
- const guard = { handler, inputMessages, parentSpan: undefined }
233
- guard.getInputEval = () => (inputEvalPromise ??= publishEvaluation(inputMessages, guard.parentSpan))
234
- return guard
235
- }
236
-
237
- /**
238
- * Wraps `apiProm.asResponse` so callers that consume the raw `Response` object still
239
- * receive the Before Model verdict. After Model evaluation is not performed on this
240
- * path because the response body has not been parsed.
241
- *
242
- * @param {object} apiProm - APIPromise returned from the OpenAI SDK method
243
- * @param {Guard} guard
244
- */
245
- function wrapAsResponse (apiProm, guard) {
246
- if (typeof apiProm.asResponse !== 'function') return
247
- shimmer.wrap(apiProm, 'asResponse', origAsResponse => function (...args) {
248
- const responsePromise = origAsResponse.apply(this, args)
249
- return Promise.all([guard.getInputEval(), responsePromise]).then(([, response]) => response)
250
- })
251
- }
252
-
253
- /**
254
- * Gates the parsed-body promise on Before Model evaluation. Resolves to the SDK's
255
- * result only once the Before Model verdict is in.
256
- *
257
- * @param {Promise<unknown>} parsedPromise
258
- * @param {Guard} guard
259
- * @returns {Promise<unknown>}
260
- */
261
- function gateParse (parsedPromise, guard) {
262
- return Promise.all([guard.getInputEval(), parsedPromise]).then(([, result]) => result)
263
- }
264
-
265
- /**
266
- * Runs After Model evaluation against the response body.
267
- *
268
- * @param {Guard} guard
269
- * @param {object} body - Parsed OpenAI response body
270
- * @returns {Promise<unknown>}
271
- */
272
- function evaluateOutput (guard, body) {
273
- const outputMessages = guard.handler.getOutputMessages(body)
274
- if (!outputMessages.length) return Promise.resolve()
275
- return guard.handler.publishOutputEvaluation(guard.inputMessages, outputMessages, guard.parentSpan)
276
- }
277
-
278
- module.exports = {
279
- hasSubscribers,
280
- createGuard,
281
- wrapAsResponse,
282
- gateParse,
283
- evaluateOutput,
284
- }