dd-trace 5.98.0 → 5.99.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 +0 -1
- package/ext/tags.js +1 -0
- package/index.d.ts +9 -1
- package/package.json +48 -46
- package/packages/datadog-instrumentations/src/crypto.js +45 -0
- package/packages/datadog-instrumentations/src/cypress-config.js +122 -16
- package/packages/datadog-instrumentations/src/dns.js +24 -56
- package/packages/datadog-instrumentations/src/graphql.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +74 -0
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +4 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +10 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +59 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +11 -2
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +7 -0
- package/packages/datadog-instrumentations/src/pino.js +4 -28
- package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +27 -0
- package/packages/datadog-instrumentations/src/playwright.js +5 -17
- package/packages/datadog-instrumentations/src/stripe.js +38 -24
- package/packages/datadog-instrumentations/src/vitest.js +32 -4
- package/packages/datadog-instrumentations/src/zlib.js +29 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +8 -15
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +4 -9
- package/packages/datadog-plugin-cucumber/src/index.js +2 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +48 -1
- package/packages/datadog-plugin-http/src/server.js +11 -11
- package/packages/datadog-plugin-jest/src/index.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +1 -2
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/index.js +24 -0
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +55 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +1 -6
- package/packages/datadog-plugin-playwright/src/index.js +2 -3
- package/packages/datadog-plugin-vitest/src/index.js +14 -6
- package/packages/datadog-plugin-ws/src/close.js +2 -0
- package/packages/datadog-plugin-ws/src/producer.js +2 -0
- package/packages/datadog-plugin-ws/src/receiver.js +1 -0
- package/packages/dd-trace/src/aiguard/channels.js +8 -0
- package/packages/dd-trace/src/aiguard/index.js +7 -3
- package/packages/dd-trace/src/aiguard/sdk.js +44 -0
- package/packages/dd-trace/src/aiguard/tags.js +1 -0
- package/packages/dd-trace/src/appsec/graphql.js +6 -6
- package/packages/dd-trace/src/appsec/index.js +9 -11
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -5
- package/packages/dd-trace/src/appsec/rasp/lfi.js +8 -4
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +5 -10
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +5 -6
- package/packages/dd-trace/src/appsec/recommended.json +2438 -13
- package/packages/dd-trace/src/appsec/reporter.js +6 -5
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +4 -8
- package/packages/dd-trace/src/appsec/store.js +50 -0
- package/packages/dd-trace/src/appsec/waf/index.js +3 -5
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +3 -4
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -2
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +4 -5
- package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +3 -4
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +6 -6
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +2 -2
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +2 -2
- package/packages/dd-trace/src/config/config-types.d.ts +0 -4
- package/packages/dd-trace/src/config/defaults.js +10 -10
- package/packages/dd-trace/src/config/generated-config-types.d.ts +13 -12
- package/packages/dd-trace/src/config/index.js +25 -35
- package/packages/dd-trace/src/config/parsers.js +26 -9
- package/packages/dd-trace/src/config/supported-configurations.json +32 -36
- package/packages/dd-trace/src/debugger/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +25 -5
- package/packages/dd-trace/src/encode/0.4.js +4 -5
- package/packages/dd-trace/src/exporters/agent/index.js +0 -1
- package/packages/dd-trace/src/exporters/agent/writer.js +1 -2
- package/packages/dd-trace/src/exporters/agentless/writer.js +3 -3
- package/packages/dd-trace/src/exporters/common/util.js +2 -2
- package/packages/dd-trace/src/id.js +2 -0
- package/packages/dd-trace/src/index.js +2 -5
- package/packages/dd-trace/src/lambda/handler.js +1 -3
- package/packages/dd-trace/src/llmobs/plugins/{anthropic.js → anthropic/index.js} +5 -63
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +106 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +3 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +3 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +2 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +0 -49
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/vectorstore.js +2 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/messages.js +76 -0
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -26
- package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/index.js +68 -0
- package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/utils.js +57 -0
- package/packages/dd-trace/src/llmobs/sdk.js +2 -2
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +103 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -0
- package/packages/dd-trace/src/opentelemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +3 -2
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +3 -2
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +19 -66
- package/packages/dd-trace/src/opentelemetry/trace/index.js +11 -16
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +11 -3
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +51 -41
- package/packages/dd-trace/src/opentelemetry/tracer.js +9 -11
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -10
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/tracer.js +12 -5
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/test.js +126 -5
- package/packages/dd-trace/src/plugins/util/url.js +2 -1
- package/packages/dd-trace/src/profiling/profilers/event_plugins/crypto.js +32 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/zlib.js +19 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +35 -0
- package/packages/dd-trace/src/proxy.js +2 -8
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
- package/packages/dd-trace/src/span_processor.js +1 -2
- package/packages/dd-trace/src/tagger.js +2 -2
- package/packages/dd-trace/src/telemetry/send-data.js +5 -7
- package/packages/dd-trace/src/tracer.js +2 -2
- package/vendor/dist/ignore/LICENSE +0 -21
- package/vendor/dist/ignore/index.js +0 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{type: 'text', text: string}} TextBlock
|
|
5
|
+
* @typedef {{type: 'image'}} ImageBlock
|
|
6
|
+
* @typedef {{
|
|
7
|
+
* type: 'tool_use', text: string, name: string, id: string, input: string | Record<string, unknown>
|
|
8
|
+
* }} ToolUseBlock
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* type: 'tool_result',
|
|
11
|
+
* tool_use_id: string,
|
|
12
|
+
* content: string | Array<{type: string, text?: string}>
|
|
13
|
+
* }} ToolResultBlock
|
|
14
|
+
*
|
|
15
|
+
* @typedef {{
|
|
16
|
+
* content: string,
|
|
17
|
+
* role: string,
|
|
18
|
+
* toolCalls?: Array<{
|
|
19
|
+
* name: string,
|
|
20
|
+
* arguments: string | Record<string, unknown>,
|
|
21
|
+
* toolId: string,
|
|
22
|
+
* type: string
|
|
23
|
+
* }>,
|
|
24
|
+
* toolResults?: Array<{
|
|
25
|
+
* result: string,
|
|
26
|
+
* toolId: string,
|
|
27
|
+
* type: 'tool_result'
|
|
28
|
+
* }>
|
|
29
|
+
* }} AnthropicLlmObsMessage
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Formats tool result into LLM Observability compatible contents
|
|
34
|
+
* @param {ToolResultBlock['content']} content
|
|
35
|
+
*/
|
|
36
|
+
function formatAnthropicToolResultContent (content) {
|
|
37
|
+
if (typeof content === 'string') {
|
|
38
|
+
return content
|
|
39
|
+
} else if (Array.isArray(content)) {
|
|
40
|
+
const formattedContent = []
|
|
41
|
+
for (const toolResultBlock of content) {
|
|
42
|
+
if (toolResultBlock.text) {
|
|
43
|
+
formattedContent.push(toolResultBlock.text)
|
|
44
|
+
} else if (toolResultBlock.type === 'image') {
|
|
45
|
+
formattedContent.push('([IMAGE DETECTED])')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return formattedContent.join(',')
|
|
50
|
+
}
|
|
51
|
+
return JSON.stringify(content)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Normalizes and formats a message into LLM Observability compatible contents.
|
|
56
|
+
* Can be spread into a list of other messages.
|
|
57
|
+
*
|
|
58
|
+
* @param {AnthropicLlmObsMessage[]} messages
|
|
59
|
+
* @param {{ role: string, content: string | Array<TextBlock | ImageBlock | ToolUseBlock | ToolResultBlock> }} message
|
|
60
|
+
* @returns {void}
|
|
61
|
+
*/
|
|
62
|
+
function appendMessage (messages, { role, content }) {
|
|
63
|
+
if (typeof content === 'string') {
|
|
64
|
+
messages.push({ content, role })
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const block of content) {
|
|
69
|
+
if (block.type === 'text') {
|
|
70
|
+
messages.push({ content: block.text, role })
|
|
71
|
+
} else if (block.type === 'image') {
|
|
72
|
+
messages.push({ content: '([IMAGE DETECTED])', role })
|
|
73
|
+
} else if (block.type === 'tool_use') {
|
|
74
|
+
const { text, name, id, type } = block
|
|
75
|
+
let input = block.input
|
|
76
|
+
if (typeof input === 'string') {
|
|
77
|
+
input = JSON.parse(input)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const toolCall = {
|
|
81
|
+
name,
|
|
82
|
+
arguments: input,
|
|
83
|
+
toolId: id,
|
|
84
|
+
type,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
messages.push({ content: text ?? '', role, toolCalls: [toolCall] })
|
|
88
|
+
} else if (block.type === 'tool_result') {
|
|
89
|
+
const { content } = block
|
|
90
|
+
const formattedContent = formatAnthropicToolResultContent(content)
|
|
91
|
+
const toolResult = {
|
|
92
|
+
result: formattedContent,
|
|
93
|
+
toolId: block.tool_use_id,
|
|
94
|
+
type: 'tool_result',
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
messages.push({ content: '', role, toolResults: [toolResult] })
|
|
98
|
+
} else {
|
|
99
|
+
messages.push({ content: JSON.stringify(block), role })
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
appendMessage,
|
|
106
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { spanHasError } = require('../../../util')
|
|
4
|
+
const { formatIO } = require('../messages')
|
|
4
5
|
const LangChainLLMObsHandler = require('.')
|
|
5
6
|
|
|
6
7
|
class LangChainLLMObsChainHandler extends LangChainLLMObsHandler {
|
|
7
8
|
setMetaTags ({ span, inputs, results }) {
|
|
8
9
|
let input
|
|
9
10
|
if (inputs) {
|
|
10
|
-
input =
|
|
11
|
+
input = formatIO(inputs)
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const output = !results || spanHasError(span) ? '' :
|
|
14
|
+
const output = !results || spanHasError(span) ? '' : formatIO(results)
|
|
14
15
|
|
|
15
16
|
// chain spans will always be workflows
|
|
16
17
|
this._tagger.tagTextIO(span, input, output)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const LLMObsTagger = require('../../../tagger')
|
|
4
4
|
const { spanHasError } = require('../../../util')
|
|
5
|
+
const { getRole } = require('../messages')
|
|
5
6
|
const LangChainLLMObsHandler = require('.')
|
|
6
7
|
|
|
7
8
|
const LLM = 'llm'
|
|
@@ -22,7 +23,7 @@ class LangChainLLMObsChatModelHandler extends LangChainLLMObsHandler {
|
|
|
22
23
|
for (const messageSet of inputs) {
|
|
23
24
|
for (const message of messageSet) {
|
|
24
25
|
const content = message.content || ''
|
|
25
|
-
const role =
|
|
26
|
+
const role = getRole(message)
|
|
26
27
|
inputMessages.push({ content, role })
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -54,7 +55,7 @@ class LangChainLLMObsChatModelHandler extends LangChainLLMObsHandler {
|
|
|
54
55
|
for (const messageSet of results.generations) {
|
|
55
56
|
for (const chatCompletion of messageSet) {
|
|
56
57
|
const chatCompletionMessage = chatCompletion.message
|
|
57
|
-
const role =
|
|
58
|
+
const role = getRole(chatCompletionMessage)
|
|
58
59
|
const content = chatCompletionMessage.text || ''
|
|
59
60
|
const toolCalls = this.extractToolCalls(chatCompletionMessage)
|
|
60
61
|
outputMessages.push({ content, role, toolCalls })
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const LLMObsTagger = require('../../../tagger')
|
|
4
4
|
const { spanHasError } = require('../../../util')
|
|
5
|
+
const { formatIO } = require('../messages')
|
|
5
6
|
const LangChainLLMObsHandler = require('.')
|
|
6
7
|
|
|
7
8
|
class LangChainLLMObsEmbeddingHandler extends LangChainLLMObsHandler {
|
|
@@ -10,7 +11,7 @@ class LangChainLLMObsEmbeddingHandler extends LangChainLLMObsHandler {
|
|
|
10
11
|
let embeddingInput, embeddingOutput
|
|
11
12
|
|
|
12
13
|
if (isWorkflow) {
|
|
13
|
-
embeddingInput =
|
|
14
|
+
embeddingInput = formatIO(inputs)
|
|
14
15
|
} else {
|
|
15
16
|
const input = Array.isArray(inputs) ? inputs : [inputs]
|
|
16
17
|
embeddingInput = input.map(doc => ({ text: doc }))
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const ROLE_MAPPINGS = {
|
|
4
|
-
human: 'user',
|
|
5
|
-
ai: 'assistant',
|
|
6
|
-
system: 'system',
|
|
7
|
-
}
|
|
8
|
-
|
|
9
3
|
class LangChainLLMObsHandler {
|
|
10
4
|
constructor (tagger) {
|
|
11
5
|
/** @type {import('../../../tagger')} */
|
|
@@ -18,38 +12,6 @@ class LangChainLLMObsHandler {
|
|
|
18
12
|
|
|
19
13
|
setMetaTags () {}
|
|
20
14
|
|
|
21
|
-
formatIO (messages) {
|
|
22
|
-
if (messages.constructor.name === 'Object') { // plain JSON
|
|
23
|
-
const formatted = {}
|
|
24
|
-
for (const [key, value] of Object.entries(messages)) {
|
|
25
|
-
formatted[key] = this.formatIO(value)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return formatted
|
|
29
|
-
} else if (Array.isArray(messages)) {
|
|
30
|
-
return messages.map(message => this.formatIO(message))
|
|
31
|
-
} // either a BaseMesage type or a string
|
|
32
|
-
return this.getContentFromMessage(messages)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
getContentFromMessage (message) {
|
|
36
|
-
if (typeof message === 'string') {
|
|
37
|
-
return message
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
const messageContent = {
|
|
41
|
-
content: message.content || '',
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const role = this.getRole(message)
|
|
45
|
-
if (role) messageContent.role = role
|
|
46
|
-
|
|
47
|
-
return messageContent
|
|
48
|
-
} catch {
|
|
49
|
-
return JSON.stringify(message)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
15
|
checkTokenUsageChatOrLLMResult (results) {
|
|
54
16
|
const llmOutput = results.llmOutput
|
|
55
17
|
const tokens = {
|
|
@@ -90,17 +52,6 @@ class LangChainLLMObsHandler {
|
|
|
90
52
|
runId: runIdBase,
|
|
91
53
|
}
|
|
92
54
|
}
|
|
93
|
-
|
|
94
|
-
getRole (message) {
|
|
95
|
-
if (message.role) return ROLE_MAPPINGS[message.role] || message.role
|
|
96
|
-
|
|
97
|
-
const type = (
|
|
98
|
-
(typeof message.getType === 'function' && message.getType()) ||
|
|
99
|
-
(typeof message._getType === 'function' && message._getType())
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
return ROLE_MAPPINGS[type] || type
|
|
103
|
-
}
|
|
104
55
|
}
|
|
105
56
|
|
|
106
57
|
module.exports = LangChainLLMObsHandler
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { spanHasError } = require('../../../util')
|
|
4
|
+
const { formatIO } = require('../messages')
|
|
4
5
|
const LangChainLLMObsHandler = require('.')
|
|
5
6
|
|
|
6
7
|
class LangChainLLMObsVectorStoreHandler extends LangChainLLMObsHandler {
|
|
7
8
|
setMetaTags ({ span, inputs, results }) {
|
|
8
|
-
const input =
|
|
9
|
+
const input = formatIO(inputs)
|
|
9
10
|
if (spanHasError(span)) {
|
|
10
11
|
this._tagger.tagRetrievalIO(span, input)
|
|
11
12
|
return
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ROLE_MAPPINGS = {
|
|
4
|
+
human: 'user',
|
|
5
|
+
ai: 'assistant',
|
|
6
|
+
system: 'system',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getRole (message) {
|
|
10
|
+
if (message.role) return ROLE_MAPPINGS[message.role] || message.role
|
|
11
|
+
|
|
12
|
+
const type = (
|
|
13
|
+
(typeof message.getType === 'function' && message.getType()) ||
|
|
14
|
+
(typeof message._getType === 'function' && message._getType())
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return ROLE_MAPPINGS[type] || type
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getContentFromMessage (message) {
|
|
21
|
+
if (typeof message === 'string') {
|
|
22
|
+
return message
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const messageContent = {
|
|
26
|
+
content: message.content || '',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const role = getRole(message)
|
|
30
|
+
if (role) messageContent.role = role
|
|
31
|
+
|
|
32
|
+
return messageContent
|
|
33
|
+
} catch {
|
|
34
|
+
return JSON.stringify(message)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isBaseMessage (data) {
|
|
39
|
+
return typeof data._getType === 'function' || typeof data.getType === 'function'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatIO (data) {
|
|
43
|
+
if (data == null) return ''
|
|
44
|
+
|
|
45
|
+
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
|
46
|
+
return data
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (data.constructor?.name === 'Object') {
|
|
50
|
+
const formatted = {}
|
|
51
|
+
for (const [key, value] of Object.entries(data)) {
|
|
52
|
+
formatted[key] = formatIO(value)
|
|
53
|
+
}
|
|
54
|
+
return formatted
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(data)) {
|
|
58
|
+
return data.map(item => formatIO(item))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Only duck-typed BaseMessage instances collapse to { content, role }.
|
|
62
|
+
// Other class instances (e.g. LangChain Document) preserve their shape via JSON.stringify,
|
|
63
|
+
// otherwise they'd reduce to { content: '' } and lose data.
|
|
64
|
+
if (isBaseMessage(data)) return getContentFromMessage(data)
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
return JSON.stringify(data)
|
|
68
|
+
} catch {
|
|
69
|
+
return String(data)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
getRole,
|
|
75
|
+
formatIO,
|
|
76
|
+
}
|
|
@@ -1,36 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const LLMObsPlugin = require('../base')
|
|
4
|
+
const { formatIO } = require('../langchain/messages')
|
|
4
5
|
const { spanHasError } = require('../../util')
|
|
5
6
|
|
|
6
7
|
const streamDataMap = new WeakMap()
|
|
7
8
|
|
|
8
|
-
function formatIO (data) {
|
|
9
|
-
if (data == null) return ''
|
|
10
|
-
|
|
11
|
-
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
|
12
|
-
return data
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (data.constructor?.name === 'Object') {
|
|
16
|
-
const formatted = {}
|
|
17
|
-
for (const [key, value] of Object.entries(data)) {
|
|
18
|
-
formatted[key] = formatIO(value)
|
|
19
|
-
}
|
|
20
|
-
return formatted
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (Array.isArray(data)) {
|
|
24
|
-
return data.map(item => formatIO(item))
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
return JSON.stringify(data)
|
|
29
|
-
} catch {
|
|
30
|
-
return String(data)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
9
|
class PregelStreamLLMObsPlugin extends LLMObsPlugin {
|
|
35
10
|
static id = 'llmobs_langgraph_pregel_stream'
|
|
36
11
|
static integration = 'langgraph'
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LLMObsPlugin = require('../base')
|
|
4
|
+
const { formatInput, formatOutput } = require('./utils')
|
|
5
|
+
|
|
6
|
+
class McpToolCallLLMObsPlugin extends LLMObsPlugin {
|
|
7
|
+
static id = 'llmobs_mcp_tool_call'
|
|
8
|
+
static integration = 'modelcontextprotocol-sdk'
|
|
9
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_callTool'
|
|
10
|
+
|
|
11
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
12
|
+
const params = ctx.arguments?.[0]
|
|
13
|
+
const toolName = params?.name || 'unknown_tool'
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
kind: 'tool',
|
|
17
|
+
name: `MCP Client Tool Call: ${toolName}`,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setLLMObsTags (ctx) {
|
|
22
|
+
const span = ctx.currentStore?.span
|
|
23
|
+
if (!span) return
|
|
24
|
+
|
|
25
|
+
const params = ctx.arguments?.[0]
|
|
26
|
+
const toolName = params?.name
|
|
27
|
+
const toolArguments = params?.arguments
|
|
28
|
+
|
|
29
|
+
const spanTags = { mcp_tool_kind: 'client' }
|
|
30
|
+
|
|
31
|
+
const serverVersion = ctx.self?.getServerVersion?.()
|
|
32
|
+
if (serverVersion) {
|
|
33
|
+
if (serverVersion.name) spanTags.mcp_server_name = serverVersion.name
|
|
34
|
+
if (serverVersion.version) spanTags.mcp_server_version = serverVersion.version
|
|
35
|
+
if (serverVersion.title) spanTags.mcp_server_title = serverVersion.title
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this._tagger.tagSpanTags(span, spanTags)
|
|
39
|
+
|
|
40
|
+
const hasError = ctx.error || ctx.result?.isError
|
|
41
|
+
const input = formatInput(toolName, toolArguments)
|
|
42
|
+
const output = hasError ? undefined : formatOutput(ctx.result)
|
|
43
|
+
|
|
44
|
+
this._tagger.tagTextIO(span, input, output)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class McpListToolsLLMObsPlugin extends LLMObsPlugin {
|
|
49
|
+
static id = 'llmobs_mcp_list_tools'
|
|
50
|
+
static integration = 'modelcontextprotocol-sdk'
|
|
51
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_listTools'
|
|
52
|
+
|
|
53
|
+
getLLMObsSpanRegisterOptions () {
|
|
54
|
+
return {
|
|
55
|
+
kind: 'task',
|
|
56
|
+
name: 'MCP Client List Tools',
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setLLMObsTags (ctx) {
|
|
61
|
+
const span = ctx.currentStore?.span
|
|
62
|
+
if (!span || ctx.error) return
|
|
63
|
+
|
|
64
|
+
this._tagger.tagTextIO(span, null, JSON.stringify(ctx.result))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = [McpToolCallLLMObsPlugin, McpListToolsLLMObsPlugin]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Formats tool call input as a JSON string.
|
|
5
|
+
* @param {string} toolName - The name of the tool being called
|
|
6
|
+
* @param {object} toolArguments - The arguments passed to the tool
|
|
7
|
+
* @returns {string} Formatted input string
|
|
8
|
+
*/
|
|
9
|
+
function formatInput (toolName, toolArguments) {
|
|
10
|
+
if (!toolName && !toolArguments) return ''
|
|
11
|
+
|
|
12
|
+
if (toolArguments === undefined || toolArguments === null) {
|
|
13
|
+
return toolName || ''
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify({ name: toolName, arguments: toolArguments })
|
|
18
|
+
} catch {
|
|
19
|
+
return toolName || ''
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Formats MCP tool call result as a structured object matching Python's output format.
|
|
25
|
+
* MCP tool results contain a `content` array with items like:
|
|
26
|
+
* `[{ type: 'text', text: '...' }, { type: 'image', data: '...', mimeType: '...' }]`
|
|
27
|
+
* @param {object} result - The MCP CallToolResult
|
|
28
|
+
* @returns {string} JSON string of `{ content: Array<{type, text, annotations, meta}>, isError: boolean }`
|
|
29
|
+
*/
|
|
30
|
+
function formatOutput (result) {
|
|
31
|
+
if (!result) return ''
|
|
32
|
+
|
|
33
|
+
const content = result.content
|
|
34
|
+
const isError = result.isError || false
|
|
35
|
+
|
|
36
|
+
const processed = []
|
|
37
|
+
if (Array.isArray(content)) {
|
|
38
|
+
for (const item of content) {
|
|
39
|
+
if (item.type !== 'text') continue
|
|
40
|
+
const contentBlock = {
|
|
41
|
+
type: item.type,
|
|
42
|
+
text: item.text || '',
|
|
43
|
+
annotations: item.annotations || {},
|
|
44
|
+
meta: item._meta || {},
|
|
45
|
+
}
|
|
46
|
+
processed.push(contentBlock)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
return JSON.stringify({ content: processed, isError })
|
|
52
|
+
} catch {
|
|
53
|
+
return ''
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { formatInput, formatOutput }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { isError, isTrue } = require('../util')
|
|
6
6
|
const tracerVersion = require('../../../../package.json').version
|
|
7
7
|
const logger = require('../log')
|
|
8
8
|
const { getValueFromEnvSources } = require('../config/helper')
|
|
@@ -427,7 +427,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
// When OTel tracing is enabled, add source:otel tag to allow backend to wait for OTel span conversion
|
|
430
|
-
if (
|
|
430
|
+
if (this._config.DD_TRACE_OTEL_ENABLED) {
|
|
431
431
|
evaluationTags.source = 'otel'
|
|
432
432
|
}
|
|
433
433
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../log')
|
|
4
|
+
|
|
5
|
+
const METER_NAME = 'dd-trace-js/openfeature'
|
|
6
|
+
const COUNTER_NAME = 'feature_flag.evaluations'
|
|
7
|
+
const COUNTER_DESCRIPTION = 'Number of feature flag evaluations'
|
|
8
|
+
const COUNTER_UNIT = '{evaluation}'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* OpenFeature hook that tracks feature flag evaluation metrics using an
|
|
12
|
+
* OpenTelemetry counter.
|
|
13
|
+
*
|
|
14
|
+
* Implements the OpenFeature `finally` hook interface so it can be pushed
|
|
15
|
+
* directly onto a provider's `hooks` array. We use the `finally` stage
|
|
16
|
+
* (not diagnostic channels inside the provider's `resolve*` methods) because
|
|
17
|
+
* the OpenFeature SDK short-circuits before calling the provider when it is in
|
|
18
|
+
* NOT_READY state; the `finally` hook still fires, ensuring all evaluations are
|
|
19
|
+
* captured. It also catches type-mismatch errors detected by the SDK client
|
|
20
|
+
* after the provider returns.
|
|
21
|
+
*
|
|
22
|
+
* The counter is created lazily on the first successful `finally()` call rather
|
|
23
|
+
* than in the constructor. This is necessary because `FlaggingProvider` is
|
|
24
|
+
* constructed eagerly by `proxy.js#updateTracing()`, which runs *before*
|
|
25
|
+
* `initializeOpenTelemetryMetrics()` sets the global OTel meter provider.
|
|
26
|
+
* Calling `getMeter()` in the constructor would return the noop meter and
|
|
27
|
+
* produce a noop counter that silently discards all measurements. By deferring
|
|
28
|
+
* to `finally()` time we give the meter provider a chance to be set up first.
|
|
29
|
+
*
|
|
30
|
+
* If counter creation fails (e.g. the OTel API is not yet available), the call
|
|
31
|
+
* is silently skipped and retried on the next `finally()` invocation.
|
|
32
|
+
*
|
|
33
|
+
* When `config.otelMetricsEnabled` is false, `finally()` is always a no-op.
|
|
34
|
+
*/
|
|
35
|
+
class EvalMetricsHook {
|
|
36
|
+
#enabled = false
|
|
37
|
+
#counter = null
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {import('../config')} config - Tracer configuration object
|
|
41
|
+
*/
|
|
42
|
+
constructor (config) {
|
|
43
|
+
this.#enabled = config.otelMetricsEnabled === true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns the OTel counter, creating it on first successful call.
|
|
48
|
+
* Returns `null` if counter creation fails; will retry on next call.
|
|
49
|
+
*
|
|
50
|
+
* @returns {import('@opentelemetry/api').Counter | null}
|
|
51
|
+
*/
|
|
52
|
+
#getCounter () {
|
|
53
|
+
if (this.#counter) return this.#counter
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const { metrics } = require('@opentelemetry/api')
|
|
57
|
+
const meter = metrics.getMeter(METER_NAME)
|
|
58
|
+
this.#counter = meter.createCounter(COUNTER_NAME, {
|
|
59
|
+
description: COUNTER_DESCRIPTION,
|
|
60
|
+
unit: COUNTER_UNIT,
|
|
61
|
+
})
|
|
62
|
+
} catch (e) {
|
|
63
|
+
log.warn('EvalMetricsHook: failed to create counter: %s', e.message)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return this.#counter
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Called by the OpenFeature SDK after every flag evaluation (success or error).
|
|
71
|
+
*
|
|
72
|
+
* @param {{ flagKey: string }} hookContext - Hook context containing the flag key
|
|
73
|
+
* @param {{ variant?: string, reason?: string, errorCode?: string, flagMetadata?: object }} evaluationDetails
|
|
74
|
+
* - Full evaluation details
|
|
75
|
+
* @returns {void}
|
|
76
|
+
*/
|
|
77
|
+
finally (hookContext, evaluationDetails) {
|
|
78
|
+
if (!this.#enabled) return
|
|
79
|
+
|
|
80
|
+
const counter = this.#getCounter()
|
|
81
|
+
if (!counter) return
|
|
82
|
+
|
|
83
|
+
const attributes = {
|
|
84
|
+
'feature_flag.key': hookContext?.flagKey ?? '',
|
|
85
|
+
'feature_flag.result.variant': evaluationDetails?.variant ?? '',
|
|
86
|
+
'feature_flag.result.reason': evaluationDetails?.reason?.toLowerCase() ?? 'unknown',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const errorCode = evaluationDetails?.errorCode
|
|
90
|
+
if (errorCode) {
|
|
91
|
+
attributes['error.type'] = errorCode.toLowerCase()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const allocationKey = evaluationDetails?.flagMetadata?.allocationKey
|
|
95
|
+
if (allocationKey) {
|
|
96
|
+
attributes['feature_flag.result.allocation_key'] = allocationKey
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
counter.add(1, attributes)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = EvalMetricsHook
|
|
@@ -4,6 +4,7 @@ const { DatadogNodeServerProvider } = require('@datadog/openfeature-node-server'
|
|
|
4
4
|
const { channel } = require('dc-polyfill')
|
|
5
5
|
const log = require('../log')
|
|
6
6
|
const { EXPOSURE_CHANNEL } = require('./constants/constants')
|
|
7
|
+
const EvalMetricsHook = require('./eval-metrics-hook')
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* OpenFeature provider that integrates with Datadog's feature flagging system.
|
|
@@ -24,6 +25,8 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
24
25
|
this._tracer = tracer
|
|
25
26
|
this._config = config
|
|
26
27
|
|
|
28
|
+
this.hooks.push(new EvalMetricsHook(config))
|
|
29
|
+
|
|
27
30
|
log.debug('%s created with timeout: %dms', this.constructor.name,
|
|
28
31
|
config.experimental.flaggingProvider.initializationTimeoutMs)
|
|
29
32
|
}
|
|
@@ -61,7 +61,7 @@ function initializeOpenTelemetryLogs (config) {
|
|
|
61
61
|
// Create OTLP exporter using resolved config values
|
|
62
62
|
const exporter = new OtlpHttpLogExporter(
|
|
63
63
|
config.otelLogsUrl,
|
|
64
|
-
config.
|
|
64
|
+
config.OTEL_EXPORTER_OTLP_LOGS_HEADERS,
|
|
65
65
|
config.otelLogsTimeout,
|
|
66
66
|
config.otelLogsProtocol,
|
|
67
67
|
resourceAttributes
|
|
@@ -22,13 +22,14 @@ class OtlpHttpLogExporter extends OtlpHttpExporterBase {
|
|
|
22
22
|
* Creates a new OtlpHttpLogExporter instance.
|
|
23
23
|
*
|
|
24
24
|
* @param {string} url - OTLP endpoint URL
|
|
25
|
-
* @param {string} headers - Additional HTTP headers
|
|
25
|
+
* @param {Record<string, string>|undefined} headers - Additional HTTP headers parsed from the
|
|
26
|
+
* corresponding `OTEL_EXPORTER_OTLP_*_HEADERS` env by the MAP parser.
|
|
26
27
|
* @param {number} timeout - Request timeout in milliseconds
|
|
27
28
|
* @param {string} protocol - OTLP protocol (http/protobuf or http/json)
|
|
28
29
|
* @param {Resource} resource - Resource attributes
|
|
29
30
|
*/
|
|
30
31
|
constructor (url, headers, timeout, protocol, resource) {
|
|
31
|
-
super(url, headers, timeout, protocol, '
|
|
32
|
+
super(url, headers, timeout, protocol, 'logs')
|
|
32
33
|
this.transformer = new OtlpTransformer(resource, protocol)
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -58,7 +58,7 @@ function initializeOpenTelemetryMetrics (config) {
|
|
|
58
58
|
|
|
59
59
|
const exporter = new OtlpHttpMetricExporter(
|
|
60
60
|
config.otelMetricsUrl,
|
|
61
|
-
config.
|
|
61
|
+
config.OTEL_EXPORTER_OTLP_METRICS_HEADERS,
|
|
62
62
|
config.otelMetricsTimeout,
|
|
63
63
|
config.otelMetricsProtocol,
|
|
64
64
|
resourceAttributes
|