dd-trace 5.104.0 → 5.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +90 -102
- package/index.d.ts +82 -3
- package/package.json +15 -15
- package/packages/datadog-core/src/storage.js +1 -1
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/ai.js +8 -7
- package/packages/datadog-instrumentations/src/aws-sdk.js +13 -0
- package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
- package/packages/datadog-instrumentations/src/cucumber.js +78 -5
- package/packages/datadog-instrumentations/src/dns.js +54 -18
- package/packages/datadog-instrumentations/src/fastify.js +142 -82
- package/packages/datadog-instrumentations/src/graphql.js +188 -62
- package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
- package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
- package/packages/datadog-instrumentations/src/hono.js +54 -3
- package/packages/datadog-instrumentations/src/http/server.js +9 -4
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
- package/packages/datadog-instrumentations/src/jest.js +360 -150
- package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
- package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
- package/packages/datadog-instrumentations/src/nats.js +182 -0
- package/packages/datadog-instrumentations/src/nyc.js +38 -1
- package/packages/datadog-instrumentations/src/openai.js +33 -18
- package/packages/datadog-instrumentations/src/oracledb.js +6 -1
- package/packages/datadog-instrumentations/src/pino.js +17 -5
- package/packages/datadog-instrumentations/src/playwright.js +515 -292
- package/packages/datadog-instrumentations/src/router.js +76 -32
- package/packages/datadog-instrumentations/src/stripe.js +1 -1
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
- package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
- package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
- package/packages/datadog-plugin-bunyan/src/index.js +28 -0
- package/packages/datadog-plugin-cucumber/src/index.js +17 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
- package/packages/datadog-plugin-cypress/src/support.js +69 -1
- package/packages/datadog-plugin-dns/src/lookup.js +8 -6
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +2 -0
- package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
- package/packages/datadog-plugin-http/src/server.js +40 -15
- package/packages/datadog-plugin-jest/src/index.js +11 -3
- package/packages/datadog-plugin-jest/src/util.js +15 -8
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
- package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
- package/packages/datadog-plugin-mocha/src/index.js +19 -4
- package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
- package/packages/datadog-plugin-nats/src/consumer.js +43 -0
- package/packages/datadog-plugin-nats/src/index.js +20 -0
- package/packages/datadog-plugin-nats/src/producer.js +62 -0
- package/packages/datadog-plugin-nats/src/util.js +33 -0
- package/packages/datadog-plugin-next/src/index.js +5 -3
- package/packages/datadog-plugin-openai/src/tracing.js +15 -2
- package/packages/datadog-plugin-oracledb/src/index.js +13 -2
- package/packages/datadog-plugin-pino/src/index.js +42 -0
- package/packages/datadog-plugin-playwright/src/index.js +4 -4
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-router/src/index.js +33 -44
- package/packages/datadog-plugin-selenium/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +5 -13
- package/packages/datadog-plugin-winston/src/index.js +30 -0
- package/packages/datadog-shimmer/src/shimmer.js +33 -40
- package/packages/dd-trace/src/aiguard/index.js +1 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
- package/packages/dd-trace/src/appsec/index.js +1 -1
- package/packages/dd-trace/src/appsec/reporter.js +5 -6
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
- package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
- package/packages/dd-trace/src/baggage.js +7 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
- package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
- package/packages/dd-trace/src/config/supported-configurations.json +27 -8
- package/packages/dd-trace/src/datastreams/writer.js +2 -4
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
- package/packages/dd-trace/src/encode/0.4.js +124 -108
- package/packages/dd-trace/src/encode/0.5.js +114 -26
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
- package/packages/dd-trace/src/encode/agentless-json.js +4 -2
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
- package/packages/dd-trace/src/encode/span-stats.js +16 -16
- package/packages/dd-trace/src/encode/tags-processors.js +16 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +0 -16
- package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
- package/packages/dd-trace/src/llmobs/tagger.js +9 -1
- package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/util.js +66 -3
- package/packages/dd-trace/src/log/index.js +1 -1
- package/packages/dd-trace/src/msgpack/chunk.js +394 -10
- package/packages/dd-trace/src/msgpack/index.js +96 -2
- package/packages/dd-trace/src/openfeature/encoding.js +70 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
- package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
- package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
- package/packages/dd-trace/src/opentracing/span.js +59 -19
- package/packages/dd-trace/src/opentracing/span_context.js +49 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
- package/packages/dd-trace/src/plugins/database.js +7 -6
- package/packages/dd-trace/src/plugins/index.js +4 -0
- package/packages/dd-trace/src/plugins/log_injection.js +56 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
- package/packages/dd-trace/src/plugins/outbound.js +1 -1
- package/packages/dd-trace/src/plugins/plugin.js +15 -17
- package/packages/dd-trace/src/plugins/tracing.js +43 -5
- package/packages/dd-trace/src/plugins/util/test.js +236 -13
- package/packages/dd-trace/src/plugins/util/web.js +79 -65
- package/packages/dd-trace/src/priority_sampler.js +2 -2
- package/packages/dd-trace/src/profiling/profiler.js +2 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
- package/packages/dd-trace/src/sampling_rule.js +7 -7
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
- package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
- package/packages/dd-trace/src/span_format.js +190 -58
- package/packages/dd-trace/src/spanleak.js +1 -1
- package/packages/dd-trace/src/standalone/index.js +3 -3
- package/packages/dd-trace/src/tagger.js +0 -2
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
- package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/packages/dd-trace/src/msgpack/encoder.js +0 -308
- package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Returns the value as a string, JSON-stringifying it when it is not already a string.
|
|
5
|
+
* Returns the value unchanged when it is `null` or `undefined`.
|
|
6
|
+
*
|
|
7
|
+
* @param {unknown} value
|
|
8
|
+
* @returns {string|undefined|null}
|
|
9
|
+
*/
|
|
10
|
+
function stringifyIfNeeded (value) {
|
|
11
|
+
if (value == null) return value
|
|
12
|
+
return typeof value === 'string' ? value : JSON.stringify(value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const FILE_FALLBACK = '[file]'
|
|
16
|
+
const IMAGE_FALLBACK = '[image]'
|
|
17
|
+
|
|
18
|
+
const OPENAI_RESPONSE_TOOL_CALL_TYPES = new Set([
|
|
19
|
+
'apply_patch_call',
|
|
20
|
+
'code_interpreter_call',
|
|
21
|
+
'computer_call',
|
|
22
|
+
'custom_tool_call',
|
|
23
|
+
'file_search_call',
|
|
24
|
+
'function_call',
|
|
25
|
+
'image_generation_call',
|
|
26
|
+
'local_shell_call',
|
|
27
|
+
'mcp_call',
|
|
28
|
+
'shell_call',
|
|
29
|
+
'web_search_call',
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
const OPENAI_RESPONSE_TOOL_OUTPUT_TYPES = new Set([
|
|
33
|
+
'apply_patch_call_output',
|
|
34
|
+
'computer_call_output',
|
|
35
|
+
'custom_tool_call_output',
|
|
36
|
+
'function_call_output',
|
|
37
|
+
'local_shell_call_output',
|
|
38
|
+
'shell_call_output',
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns a stringified value, falling back to an empty string for absent values.
|
|
43
|
+
*
|
|
44
|
+
* @param {unknown} value
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
function stringifyOrEmpty (value) {
|
|
48
|
+
return stringifyIfNeeded(value) ?? ''
|
|
49
|
+
}
|
|
50
|
+
|
|
3
51
|
/**
|
|
4
52
|
* Converts a LanguageModelV2FilePart with an image mediaType to an AI guard style image_url content part.
|
|
5
53
|
*
|
|
@@ -79,12 +127,11 @@ function convertVercelPromptToMessages (prompt) {
|
|
|
79
127
|
if (part.type === 'text') {
|
|
80
128
|
textParts.push(part.text)
|
|
81
129
|
} else if (part.type === 'tool-call') {
|
|
82
|
-
const args = part.args ?? part.input
|
|
83
130
|
toolCalls.push({
|
|
84
131
|
id: part.toolCallId,
|
|
85
132
|
function: {
|
|
86
133
|
name: part.toolName,
|
|
87
|
-
arguments:
|
|
134
|
+
arguments: stringifyIfNeeded(part.args ?? part.input),
|
|
88
135
|
},
|
|
89
136
|
})
|
|
90
137
|
}
|
|
@@ -103,11 +150,10 @@ function convertVercelPromptToMessages (prompt) {
|
|
|
103
150
|
|
|
104
151
|
for (const part of msg.content) {
|
|
105
152
|
if (part.type === 'tool-result') {
|
|
106
|
-
const result = part.result ?? part.output
|
|
107
153
|
messages.push({
|
|
108
154
|
role: 'tool',
|
|
109
155
|
tool_call_id: part.toolCallId,
|
|
110
|
-
content:
|
|
156
|
+
content: stringifyIfNeeded(part.result ?? part.output),
|
|
111
157
|
})
|
|
112
158
|
}
|
|
113
159
|
}
|
|
@@ -118,6 +164,58 @@ function convertVercelPromptToMessages (prompt) {
|
|
|
118
164
|
return messages
|
|
119
165
|
}
|
|
120
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Converts OpenAI chat-completions messages to the message format expected by AI Guard.
|
|
169
|
+
*
|
|
170
|
+
* Modern `tool_calls` messages already match the expected shape. Deprecated chat
|
|
171
|
+
* completions `function_call` and `function` role messages are normalized to the
|
|
172
|
+
* equivalent tool-call shape so AI Guard can classify them as tool interactions.
|
|
173
|
+
*
|
|
174
|
+
* @param {Array<object>} messages
|
|
175
|
+
* @returns {Array<object>|undefined}
|
|
176
|
+
*/
|
|
177
|
+
function normalizeOpenAIChatMessages (messages) {
|
|
178
|
+
if (!Array.isArray(messages) || messages.length === 0) return
|
|
179
|
+
|
|
180
|
+
const normalizedMessages = []
|
|
181
|
+
for (const message of messages) {
|
|
182
|
+
const normalized = normalizeOpenAIChatMessage(message)
|
|
183
|
+
if (normalized) normalizedMessages.push(normalized)
|
|
184
|
+
}
|
|
185
|
+
return normalizedMessages.length ? normalizedMessages : undefined
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Converts one OpenAI chat-completions message to AI Guard's expected shape.
|
|
190
|
+
*
|
|
191
|
+
* @param {object} message
|
|
192
|
+
* @returns {object|undefined}
|
|
193
|
+
*/
|
|
194
|
+
function normalizeOpenAIChatMessage (message) {
|
|
195
|
+
if (!message || typeof message !== 'object') return
|
|
196
|
+
|
|
197
|
+
if (message.role === 'function') {
|
|
198
|
+
return {
|
|
199
|
+
role: 'tool',
|
|
200
|
+
tool_call_id: message.tool_call_id ?? message.name,
|
|
201
|
+
content: stringifyOrEmpty(message.content),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!message.function_call) return message
|
|
206
|
+
|
|
207
|
+
const { function_call: functionCall, ...normalized } = message
|
|
208
|
+
const name = functionCall.name
|
|
209
|
+
normalized.tool_calls ??= [{
|
|
210
|
+
id: message.tool_call_id ?? name,
|
|
211
|
+
function: {
|
|
212
|
+
name,
|
|
213
|
+
arguments: stringifyOrEmpty(functionCall.arguments),
|
|
214
|
+
},
|
|
215
|
+
}]
|
|
216
|
+
return normalized
|
|
217
|
+
}
|
|
218
|
+
|
|
121
219
|
/**
|
|
122
220
|
* Converts LLM output tool calls to AI guard style message format.
|
|
123
221
|
*
|
|
@@ -130,16 +228,13 @@ function buildToolCallOutputMessages (inputMessages, toolCalls) {
|
|
|
130
228
|
...inputMessages,
|
|
131
229
|
{
|
|
132
230
|
role: 'assistant',
|
|
133
|
-
tool_calls: toolCalls.map(tc => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
},
|
|
141
|
-
}
|
|
142
|
-
}),
|
|
231
|
+
tool_calls: toolCalls.map(tc => ({
|
|
232
|
+
id: tc.toolCallId,
|
|
233
|
+
function: {
|
|
234
|
+
name: tc.toolName,
|
|
235
|
+
arguments: stringifyIfNeeded(tc.args ?? tc.input),
|
|
236
|
+
},
|
|
237
|
+
})),
|
|
143
238
|
},
|
|
144
239
|
]
|
|
145
240
|
}
|
|
@@ -173,10 +268,223 @@ function buildOutputMessages (inputMessages, content) {
|
|
|
173
268
|
return inputMessages
|
|
174
269
|
}
|
|
175
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Converts OpenAI Responses API input/output items to OpenAI chat-style messages.
|
|
273
|
+
*
|
|
274
|
+
* @param {string|Array<object>|undefined} items
|
|
275
|
+
* @param {string} defaultRole
|
|
276
|
+
* @returns {Array<object>}
|
|
277
|
+
*/
|
|
278
|
+
function convertOpenAIResponseItemsToMessages (items, defaultRole) {
|
|
279
|
+
if (typeof items === 'string') return [{ role: defaultRole, content: items }]
|
|
280
|
+
if (!Array.isArray(items)) return []
|
|
281
|
+
|
|
282
|
+
const messages = []
|
|
283
|
+
for (const item of items) {
|
|
284
|
+
const converted = openAIResponseItemToMessage(item, defaultRole)
|
|
285
|
+
if (Array.isArray(converted)) {
|
|
286
|
+
for (const message of converted) messages.push(message)
|
|
287
|
+
} else if (converted) {
|
|
288
|
+
messages.push(converted)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return messages
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Converts OpenAI reusable prompt variables to user messages for AI Guard.
|
|
296
|
+
*
|
|
297
|
+
* The reusable prompt template body is not available on the request, but its
|
|
298
|
+
* variables are user/application-provided content that OpenAI substitutes into
|
|
299
|
+
* the prompt. Screening them closes prompt-only `responses.create({ prompt })`
|
|
300
|
+
* calls and prompt variables used alongside `input`.
|
|
301
|
+
*
|
|
302
|
+
* @param {{variables?: Record<string, string|object>|null}|undefined|null} prompt
|
|
303
|
+
* @returns {Array<object>}
|
|
304
|
+
*/
|
|
305
|
+
function convertOpenAIResponsePromptToMessages (prompt) {
|
|
306
|
+
const variables = prompt?.variables
|
|
307
|
+
if (!variables || typeof variables !== 'object') return []
|
|
308
|
+
|
|
309
|
+
const messages = []
|
|
310
|
+
for (const value of Object.values(variables)) {
|
|
311
|
+
const content = openAIResponsePromptVariableToMessageContent(value)
|
|
312
|
+
if (content != null) messages.push({ role: 'user', content })
|
|
313
|
+
}
|
|
314
|
+
return messages
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Converts one OpenAI reusable prompt variable value to message content.
|
|
319
|
+
*
|
|
320
|
+
* Routes every variable through `openAIResponseContentToMessageContent` so the
|
|
321
|
+
* result follows the same string-when-text-only / array-when-multimodal shape
|
|
322
|
+
* convention used elsewhere in this file. Media variables that produce no
|
|
323
|
+
* usable content (e.g. an `input_image` with no URL or `file_id`) fall back to
|
|
324
|
+
* a stable text marker so AI Guard still observes that a media variable was
|
|
325
|
+
* attached.
|
|
326
|
+
*
|
|
327
|
+
* @param {string|object} value
|
|
328
|
+
* @returns {string|Array<{type: string, text?: string, image_url?: {url: string}}>|undefined}
|
|
329
|
+
*/
|
|
330
|
+
function openAIResponsePromptVariableToMessageContent (value) {
|
|
331
|
+
let part
|
|
332
|
+
if (typeof value === 'string') {
|
|
333
|
+
part = { type: 'input_text', text: value }
|
|
334
|
+
} else if (value && typeof value === 'object') {
|
|
335
|
+
part = value
|
|
336
|
+
} else {
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const content = openAIResponseContentToMessageContent([part])
|
|
341
|
+
if (content != null) return content
|
|
342
|
+
if (part.type === 'input_image') return IMAGE_FALLBACK
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Converts one OpenAI Responses API item to an OpenAI chat-style message.
|
|
347
|
+
*
|
|
348
|
+
* @param {object} item
|
|
349
|
+
* @param {string} defaultRole
|
|
350
|
+
* @returns {object|Array<object>|undefined}
|
|
351
|
+
*/
|
|
352
|
+
function openAIResponseItemToMessage (item, defaultRole) {
|
|
353
|
+
if (!item || typeof item !== 'object') return
|
|
354
|
+
const type = item.type ?? 'message'
|
|
355
|
+
|
|
356
|
+
if (type === 'message') {
|
|
357
|
+
const content = openAIResponseContentToMessageContent(item.content)
|
|
358
|
+
if (content != null) return { role: item.role || defaultRole, content }
|
|
359
|
+
} else if (OPENAI_RESPONSE_TOOL_CALL_TYPES.has(type)) {
|
|
360
|
+
return openAIResponseToolCallToMessages(item)
|
|
361
|
+
} else if (OPENAI_RESPONSE_TOOL_OUTPUT_TYPES.has(type)) {
|
|
362
|
+
return openAIResponseToolOutputToMessage(item)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Converts a Responses API tool-call item to one or more chat-style messages.
|
|
368
|
+
*
|
|
369
|
+
* Most tool-call items represent only the assistant's tool request. MCP and
|
|
370
|
+
* image-generation items can also carry tool output on the same item, so include
|
|
371
|
+
* a linked tool message when output-like fields are present.
|
|
372
|
+
*
|
|
373
|
+
* @param {object} item
|
|
374
|
+
* @returns {object|Array<object>}
|
|
375
|
+
*/
|
|
376
|
+
function openAIResponseToolCallToMessages (item) {
|
|
377
|
+
const toolCallId = item.call_id ?? item.id ?? item.name ?? item.type
|
|
378
|
+
const message = {
|
|
379
|
+
role: 'assistant',
|
|
380
|
+
tool_calls: [{
|
|
381
|
+
id: toolCallId,
|
|
382
|
+
function: {
|
|
383
|
+
name: item.name ?? item.server_label ?? item.type,
|
|
384
|
+
arguments: stringifyOrEmpty(item.arguments ?? item.input ?? item.action),
|
|
385
|
+
},
|
|
386
|
+
}],
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (item.output == null && item.result == null && item.error == null) return message
|
|
390
|
+
return [message, openAIResponseToolOutputToMessage(item)]
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Converts a Responses API tool-output item to a chat-style tool message.
|
|
395
|
+
*
|
|
396
|
+
* @param {object} item
|
|
397
|
+
* @returns {object}
|
|
398
|
+
*/
|
|
399
|
+
function openAIResponseToolOutputToMessage (item) {
|
|
400
|
+
return {
|
|
401
|
+
role: 'tool',
|
|
402
|
+
tool_call_id: item.call_id ?? item.id,
|
|
403
|
+
content: openAIResponseOutputValueToMessageContent(item.output ?? item.result ?? item.error),
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Converts Responses API tool output to message content.
|
|
409
|
+
*
|
|
410
|
+
* @param {unknown} output
|
|
411
|
+
* @returns {string|Array<{type: string, text?: string, image_url?: {url: string}}>}
|
|
412
|
+
*/
|
|
413
|
+
function openAIResponseOutputValueToMessageContent (output) {
|
|
414
|
+
const content = openAIResponseContentToMessageContent(output)
|
|
415
|
+
return content ?? stringifyOrEmpty(output)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Converts OpenAI Responses API content to OpenAI chat-style message content.
|
|
420
|
+
*
|
|
421
|
+
* @param {string|Array<string|{type?: string, text?: string, refusal?: string,
|
|
422
|
+
* image_url?: string|{url?: string}, file_id?: string, file_url?: string,
|
|
423
|
+
* filename?: string}>|undefined} content
|
|
424
|
+
* @returns {string|Array<{type: string, text?: string, image_url?: {url: string}}>|undefined}
|
|
425
|
+
*/
|
|
426
|
+
function openAIResponseContentToMessageContent (content) {
|
|
427
|
+
if (typeof content === 'string') return content
|
|
428
|
+
if (!Array.isArray(content)) return
|
|
429
|
+
|
|
430
|
+
const parts = []
|
|
431
|
+
let hasImages = false
|
|
432
|
+
|
|
433
|
+
for (const part of content) {
|
|
434
|
+
if (!part) continue
|
|
435
|
+
if (typeof part === 'string') {
|
|
436
|
+
parts.push({ type: 'text', text: part })
|
|
437
|
+
} else if ((part.type === 'input_text' || part.type === 'output_text' || part.type === 'text') &&
|
|
438
|
+
typeof part.text === 'string') {
|
|
439
|
+
parts.push({ type: 'text', text: part.text })
|
|
440
|
+
} else if (part.type === 'refusal' && typeof part.refusal === 'string') {
|
|
441
|
+
parts.push({ type: 'text', text: part.refusal })
|
|
442
|
+
} else if (part.type === 'input_image' || part.type === 'image_url') {
|
|
443
|
+
const image = openAIResponseImageContentPart(part)
|
|
444
|
+
if (image) {
|
|
445
|
+
hasImages = true
|
|
446
|
+
parts.push(image)
|
|
447
|
+
}
|
|
448
|
+
} else if (part.type === 'input_file') {
|
|
449
|
+
parts.push({ type: 'text', text: openAIResponseFileContentPart(part) })
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!parts.length) return
|
|
454
|
+
if (hasImages) return parts
|
|
455
|
+
return parts.map(part => part.text).join('\n')
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Converts an OpenAI image content part to AI Guard image_url content.
|
|
460
|
+
*
|
|
461
|
+
* @param {{image_url?: string|{url?: string}, file_id?: string, url?: string}} part
|
|
462
|
+
* @returns {{type: 'image_url', image_url: {url: string}}|undefined}
|
|
463
|
+
*/
|
|
464
|
+
function openAIResponseImageContentPart (part) {
|
|
465
|
+
const url = typeof part.image_url === 'string' ? part.image_url : part.image_url?.url ?? part.url
|
|
466
|
+
if (url) return { type: 'image_url', image_url: { url } }
|
|
467
|
+
if (part.file_id) return { type: 'image_url', image_url: { url: part.file_id } }
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Extracts a stable text marker from an OpenAI file content part.
|
|
472
|
+
*
|
|
473
|
+
* @param {{file_id?: string|null, file_url?: string, filename?: string, file_data?: string}} part
|
|
474
|
+
* @returns {string}
|
|
475
|
+
*/
|
|
476
|
+
function openAIResponseFileContentPart (part) {
|
|
477
|
+
return part.file_id ?? part.file_url ?? part.filename ?? FILE_FALLBACK
|
|
478
|
+
}
|
|
479
|
+
|
|
176
480
|
module.exports = {
|
|
177
481
|
convertVercelPromptToMessages,
|
|
178
482
|
convertFilePartToImageUrl,
|
|
483
|
+
normalizeOpenAIChatMessages,
|
|
179
484
|
buildToolCallOutputMessages,
|
|
180
485
|
buildTextOutputMessages,
|
|
181
486
|
buildOutputMessages,
|
|
487
|
+
convertOpenAIResponseItemsToMessages,
|
|
488
|
+
convertOpenAIResponsePromptToMessages,
|
|
489
|
+
openAIResponseContentToMessageContent,
|
|
182
490
|
}
|
|
@@ -5,6 +5,7 @@ module.exports = {
|
|
|
5
5
|
child_process: () => require('../child_process'),
|
|
6
6
|
crypto: () => require('../crypto'),
|
|
7
7
|
dns: () => require('../dns'),
|
|
8
|
+
'dns/promises': () => require('../dns'),
|
|
8
9
|
fs: { serverless: false, fn: () => require('../fs') },
|
|
9
10
|
http: () => require('../http'),
|
|
10
11
|
http2: () => require('../http2'),
|
|
@@ -21,6 +22,7 @@ module.exports = {
|
|
|
21
22
|
'@modelcontextprotocol/sdk': () => require('../modelcontextprotocol-sdk'),
|
|
22
23
|
'apollo-server-core': () => require('../apollo-server-core'),
|
|
23
24
|
'@aws-sdk/smithy-client': () => require('../aws-sdk'),
|
|
25
|
+
'@azure/cosmos': { esmFirst: true, fn: () => require('../azure-cosmos') },
|
|
24
26
|
'@azure/event-hubs': () => require('../azure-event-hubs'),
|
|
25
27
|
'@azure/functions': () => require('../azure-functions'),
|
|
26
28
|
'durable-functions': () => require('../azure-durable-functions'),
|
|
@@ -48,6 +50,7 @@ module.exports = {
|
|
|
48
50
|
'@prisma/client': { esmFirst: true, fn: () => require('../prisma') },
|
|
49
51
|
'./runtime/library.js': () => require('../prisma'),
|
|
50
52
|
'@redis/client': () => require('../redis'),
|
|
53
|
+
'@smithy/core': () => require('../aws-sdk'),
|
|
51
54
|
'@smithy/smithy-client': () => require('../aws-sdk'),
|
|
52
55
|
'@vitest/runner': { esmFirst: true, fn: () => require('../vitest') },
|
|
53
56
|
aerospike: () => require('../aerospike'),
|
|
@@ -111,6 +114,7 @@ module.exports = {
|
|
|
111
114
|
multer: () => require('../multer'),
|
|
112
115
|
mysql: () => require('../mysql'),
|
|
113
116
|
mysql2: () => require('../mysql2'),
|
|
117
|
+
'@nats-io/nats-core': () => require('../nats'),
|
|
114
118
|
next: () => require('../next'),
|
|
115
119
|
'node-serialize': () => require('../node-serialize'),
|
|
116
120
|
nyc: () => require('../nyc'),
|
|
@@ -58,7 +58,8 @@ exports.getHooks = function getHooks (names) {
|
|
|
58
58
|
* @param {string} [args.file] path to file within package to instrument. Defaults to 'index.js'.
|
|
59
59
|
* @param {string} [args.filePattern] pattern to match files within package to instrument
|
|
60
60
|
* @param {boolean} [args.patchDefault] whether to patch the default export. Defaults to true.
|
|
61
|
-
* @param {(moduleExports: unknown, version: string, isIitm?: boolean) => unknown} [hook]
|
|
61
|
+
* @param {(moduleExports: unknown, version: string, isIitm?: boolean, hookMeta?: object) => unknown} [hook]
|
|
62
|
+
* Patches module exports
|
|
62
63
|
*/
|
|
63
64
|
exports.addHook = function addHook ({ name, versions, file, filePattern, patchDefault }, hook) {
|
|
64
65
|
if (!instrumentations[name]) {
|
|
@@ -0,0 +1,269 @@
|
|
|
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>) => Promise<unknown>}
|
|
19
|
+
* publishOutputEvaluation
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} Guard
|
|
24
|
+
* @property {ResourceHandler} handler
|
|
25
|
+
* @property {Array<object>} inputMessages
|
|
26
|
+
* @property {() => Promise<void>} getInputEval
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Publishes already-converted AI-style messages to the AI Guard evaluation channel.
|
|
31
|
+
*
|
|
32
|
+
* @param {Array<object>} messages - AI-style messages to evaluate.
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
function publishEvaluation (messages) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
aiguardChannel.publish({ messages, integration: 'openai', resolve, reject })
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extracts OpenAI input messages from a `chat.completions.create` call.
|
|
43
|
+
*
|
|
44
|
+
* @param {object} callArgs - First argument passed to the wrapped method
|
|
45
|
+
* @returns {Array<object>|undefined}
|
|
46
|
+
*/
|
|
47
|
+
function getChatCompletionsInputMessages (callArgs) {
|
|
48
|
+
return normalizeOpenAIChatMessages(callArgs?.messages)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extracts OpenAI output messages from a `chat.completions.create` parsed body.
|
|
53
|
+
* Includes any choice whose message carries content (including empty string),
|
|
54
|
+
* `tool_calls`, a `refusal` field, or the deprecated `function_call` field. GPT-4o
|
|
55
|
+
* emits `{content: null, refusal: "..."}` on policy refusals, and pre-tool-call
|
|
56
|
+
* SDK paths still produce `function_call`-only output — AI Guard must still see them.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} body - Parsed response body
|
|
59
|
+
* @returns {Array<object>}
|
|
60
|
+
*/
|
|
61
|
+
function getChatCompletionsOutputMessages (body) {
|
|
62
|
+
const eligible = []
|
|
63
|
+
const choices = Array.isArray(body?.choices) ? body.choices : []
|
|
64
|
+
for (const choice of choices) {
|
|
65
|
+
const message = choice?.message
|
|
66
|
+
if (
|
|
67
|
+
message?.content != null ||
|
|
68
|
+
message?.tool_calls?.length ||
|
|
69
|
+
message?.refusal != null ||
|
|
70
|
+
message?.function_call != null
|
|
71
|
+
) {
|
|
72
|
+
eligible.push(message)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return normalizeOpenAIChatMessages(eligible) ?? []
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Publishes AI Guard After Model evaluation for `chat.completions` output.
|
|
80
|
+
*
|
|
81
|
+
* Chat completions may return multiple choices when `n > 1`. Screen every choice
|
|
82
|
+
* concurrently so any unsafe assistant output rejects `.parse()`, regardless of
|
|
83
|
+
* which choice the caller ends up using.
|
|
84
|
+
*
|
|
85
|
+
* @param {Array<object>} inputMessages
|
|
86
|
+
* @param {Array<object>} outputMessages - One entry per choice
|
|
87
|
+
* @returns {Promise<Array<void>>}
|
|
88
|
+
*/
|
|
89
|
+
function publishChatCompletionsOutputEvaluation (inputMessages, outputMessages) {
|
|
90
|
+
const evals = []
|
|
91
|
+
for (const message of outputMessages) {
|
|
92
|
+
evals.push(publishEvaluation([...inputMessages, message]))
|
|
93
|
+
}
|
|
94
|
+
return Promise.all(evals)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extracts OpenAI input messages from a `responses.create` call. The `instructions`
|
|
99
|
+
* field is treated as a developer prompt — it directly steers model behavior and the
|
|
100
|
+
* LLMObs OpenAI plugin already surfaces it as one — so AI Guard must screen it too.
|
|
101
|
+
*
|
|
102
|
+
* AI Guard `/evaluate` accepts a single leading system/developer message; if the
|
|
103
|
+
* caller's `input` already begins with one, prepend the `instructions` text to its
|
|
104
|
+
* content rather than emit a second developer turn.
|
|
105
|
+
*
|
|
106
|
+
* @param {object} callArgs - First argument passed to the wrapped method
|
|
107
|
+
* @returns {Array<object>|undefined}
|
|
108
|
+
*/
|
|
109
|
+
function getResponsesInputMessages (callArgs) {
|
|
110
|
+
const messages = [
|
|
111
|
+
...convertOpenAIResponseItemsToMessages(callArgs?.input, 'user'),
|
|
112
|
+
...convertOpenAIResponsePromptToMessages(callArgs?.prompt),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
const instructions = typeof callArgs?.instructions === 'string' && callArgs.instructions.length
|
|
116
|
+
? callArgs.instructions
|
|
117
|
+
: null
|
|
118
|
+
if (!instructions) return messages.length ? messages : undefined
|
|
119
|
+
|
|
120
|
+
const first = messages[0]
|
|
121
|
+
if (first && (first.role === 'developer' || first.role === 'system')) {
|
|
122
|
+
const merged = { role: 'developer', content: mergeInstructionsWithContent(instructions, first.content) }
|
|
123
|
+
return [merged, ...messages.slice(1)]
|
|
124
|
+
}
|
|
125
|
+
return [{ role: 'developer', content: instructions }, ...messages]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Merges Responses API instructions with an existing leading developer/system content value.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} instructions
|
|
132
|
+
* @param {string|Array<object>|undefined} content
|
|
133
|
+
* @returns {string|Array<object>}
|
|
134
|
+
*/
|
|
135
|
+
function mergeInstructionsWithContent (instructions, content) {
|
|
136
|
+
if (Array.isArray(content)) return [{ type: 'text', text: instructions }, ...content]
|
|
137
|
+
if (typeof content === 'string' && content.length) return `${instructions}\n\n${content}`
|
|
138
|
+
return instructions
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Extracts OpenAI output messages from a `responses.create` parsed body.
|
|
143
|
+
*
|
|
144
|
+
* @param {object} body - Parsed response body
|
|
145
|
+
* @returns {Array<object>}
|
|
146
|
+
*/
|
|
147
|
+
function getResponsesOutputMessages (body) {
|
|
148
|
+
return convertOpenAIResponseItemsToMessages(body?.output, 'assistant')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Publishes AI Guard After Model evaluation for `responses` output.
|
|
153
|
+
*
|
|
154
|
+
* The Responses API returns a single conversation turn whose `output` items form one
|
|
155
|
+
* coherent message (reasoning steps + final assistant message + tool calls + ...);
|
|
156
|
+
* they are screened together as a single evaluation.
|
|
157
|
+
*
|
|
158
|
+
* @param {Array<object>} inputMessages
|
|
159
|
+
* @param {Array<object>} outputMessages
|
|
160
|
+
* @returns {Promise<void>}
|
|
161
|
+
*/
|
|
162
|
+
function publishResponsesOutputEvaluation (inputMessages, outputMessages) {
|
|
163
|
+
return publishEvaluation([...inputMessages, ...outputMessages])
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Per-resource handlers describing how AI Guard reads inputs and screens outputs for
|
|
168
|
+
* each LLM-prompt-accepting OpenAI endpoint. The keys also serve as the set of
|
|
169
|
+
* resources eligible for AI Guard evaluation.
|
|
170
|
+
*
|
|
171
|
+
* @type {Record<string, ResourceHandler>}
|
|
172
|
+
*/
|
|
173
|
+
const RESOURCE_HANDLERS = {
|
|
174
|
+
'chat.completions': {
|
|
175
|
+
getInputMessages: getChatCompletionsInputMessages,
|
|
176
|
+
getOutputMessages: getChatCompletionsOutputMessages,
|
|
177
|
+
publishOutputEvaluation: publishChatCompletionsOutputEvaluation,
|
|
178
|
+
},
|
|
179
|
+
responses: {
|
|
180
|
+
getInputMessages: getResponsesInputMessages,
|
|
181
|
+
getOutputMessages: getResponsesOutputMessages,
|
|
182
|
+
publishOutputEvaluation: publishResponsesOutputEvaluation,
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Reports whether the AI Guard channel has subscribers. The OpenAI instrumentation
|
|
188
|
+
* uses this to decide whether to take the AI Guard path at all.
|
|
189
|
+
*
|
|
190
|
+
* @returns {boolean}
|
|
191
|
+
*/
|
|
192
|
+
function hasSubscribers () {
|
|
193
|
+
return aiguardChannel.hasSubscribers
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Builds a guard handle when AI Guard is enabled and applicable to this call. The
|
|
198
|
+
* handle binds the per-resource handler so downstream functions never re-dispatch
|
|
199
|
+
* on `baseResource`. Returns null when AI Guard does not apply (no subscribers,
|
|
200
|
+
* non-eligible resource, streaming, or no input messages).
|
|
201
|
+
*
|
|
202
|
+
* @param {string} baseResource - e.g. `'chat.completions'` or `'responses'`
|
|
203
|
+
* @param {object} callArgs - First argument passed to the wrapped OpenAI method
|
|
204
|
+
* @param {boolean} stream - Whether the caller asked for a streamed response
|
|
205
|
+
* @returns {Guard|null}
|
|
206
|
+
*/
|
|
207
|
+
function createGuard (baseResource, callArgs, stream) {
|
|
208
|
+
// Streaming AI Guard support lands in a follow-up PR. For now, provider-level AI
|
|
209
|
+
// Guard only evaluates non-streaming responses.
|
|
210
|
+
if (stream || !aiguardChannel.hasSubscribers) return null
|
|
211
|
+
const handler = RESOURCE_HANDLERS[baseResource]
|
|
212
|
+
if (!handler) return null
|
|
213
|
+
|
|
214
|
+
const inputMessages = handler.getInputMessages(callArgs)
|
|
215
|
+
if (!inputMessages) return null
|
|
216
|
+
|
|
217
|
+
let inputEvalPromise
|
|
218
|
+
const getInputEval = () => (inputEvalPromise ??= publishEvaluation(inputMessages))
|
|
219
|
+
return { handler, inputMessages, getInputEval }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Wraps `apiProm.asResponse` so callers that consume the raw `Response` object still
|
|
224
|
+
* receive the Before Model verdict. After Model evaluation is not performed on this
|
|
225
|
+
* path because the response body has not been parsed.
|
|
226
|
+
*
|
|
227
|
+
* @param {object} apiProm - APIPromise returned from the OpenAI SDK method
|
|
228
|
+
* @param {Guard} guard
|
|
229
|
+
*/
|
|
230
|
+
function wrapAsResponse (apiProm, guard) {
|
|
231
|
+
if (typeof apiProm.asResponse !== 'function') return
|
|
232
|
+
shimmer.wrap(apiProm, 'asResponse', origAsResponse => function (...args) {
|
|
233
|
+
const responsePromise = origAsResponse.apply(this, args)
|
|
234
|
+
return Promise.all([guard.getInputEval(), responsePromise]).then(([, response]) => response)
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Gates the parsed-body promise on Before Model evaluation. Resolves to the SDK's
|
|
240
|
+
* result only once the Before Model verdict is in.
|
|
241
|
+
*
|
|
242
|
+
* @param {Promise<unknown>} parsedPromise
|
|
243
|
+
* @param {Guard} guard
|
|
244
|
+
* @returns {Promise<unknown>}
|
|
245
|
+
*/
|
|
246
|
+
function gateParse (parsedPromise, guard) {
|
|
247
|
+
return Promise.all([guard.getInputEval(), parsedPromise]).then(([, result]) => result)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Runs After Model evaluation against the response body.
|
|
252
|
+
*
|
|
253
|
+
* @param {Guard} guard
|
|
254
|
+
* @param {object} body - Parsed OpenAI response body
|
|
255
|
+
* @returns {Promise<unknown>}
|
|
256
|
+
*/
|
|
257
|
+
function evaluateOutput (guard, body) {
|
|
258
|
+
const outputMessages = guard.handler.getOutputMessages(body)
|
|
259
|
+
if (!outputMessages.length) return Promise.resolve()
|
|
260
|
+
return guard.handler.publishOutputEvaluation(guard.inputMessages, outputMessages)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = {
|
|
264
|
+
hasSubscribers,
|
|
265
|
+
createGuard,
|
|
266
|
+
wrapAsResponse,
|
|
267
|
+
gateParse,
|
|
268
|
+
evaluateOutput,
|
|
269
|
+
}
|