dd-trace 6.11.0 → 6.12.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 +5 -0
- package/index.d.ts +20 -3
- package/package.json +18 -17
- package/packages/datadog-instrumentations/src/cucumber.js +4 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +5 -2
- package/packages/datadog-instrumentations/src/playwright-reporter.js +149 -1
- package/packages/datadog-instrumentations/src/playwright.js +33 -33
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +6 -8
- package/packages/datadog-plugin-playwright/src/index.js +4 -0
- package/packages/dd-trace/src/appsec/reporter.js +2 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
- package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
- package/packages/dd-trace/src/carrier.js +107 -48
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -2
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +4 -2
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -10
- package/packages/dd-trace/src/debugger/index.js +4 -2
- package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
- package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
- package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
- package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
- package/packages/dd-trace/src/evp_proxy/path.js +22 -0
- package/packages/dd-trace/src/exporters/common/request.js +5 -2
- package/packages/dd-trace/src/llmobs/experiments/client.js +6 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +249 -11
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +23 -10
- package/packages/dd-trace/src/llmobs/experiments/index.js +18 -6
- package/packages/dd-trace/src/llmobs/experiments/noop.js +64 -4
- package/packages/dd-trace/src/llmobs/experiments/util.js +60 -4
- package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
- package/packages/dd-trace/src/openfeature/index.js +7 -4
- package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
- package/packages/dd-trace/src/openfeature/writers/exposures.js +62 -14
- package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +22 -13
- package/packages/dd-trace/src/telemetry/metrics.js +62 -8
- package/packages/dd-trace/src/telemetry/send-data.js +1 -0
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/https-proxy-agent/LICENSE +22 -0
- package/vendor/dist/https-proxy-agent/index.js +4 -0
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/proxy-from-env/LICENSE +20 -0
- package/vendor/dist/proxy-from-env/index.js +1 -0
|
@@ -55,7 +55,7 @@ function compileSegments (segments) {
|
|
|
55
55
|
? `(() => {
|
|
56
56
|
try {
|
|
57
57
|
const result = ${compile(json)}
|
|
58
|
-
return typeof result === 'string' ? result : $
|
|
58
|
+
return typeof result === 'string' ? result : $dd_inspectSegment(result)
|
|
59
59
|
} catch (e) {
|
|
60
60
|
return { expr: ${JSON.stringify(dsl)}, message: \`\${e.name}: \${e.message}\` }
|
|
61
61
|
}
|
|
@@ -4,6 +4,7 @@ const { randomUUID } = require('crypto')
|
|
|
4
4
|
const { workerData: { probeSamplerBuffer } } = require('worker_threads')
|
|
5
5
|
const { version } = require('../../../../../package.json')
|
|
6
6
|
const processTags = require('../../process-tags')
|
|
7
|
+
const { INSPECT_SEGMENT_GLOBAL_PROPERTY } = require('../constants')
|
|
7
8
|
const {
|
|
8
9
|
MAX_SAMPLED_PROBES_PER_PAUSE,
|
|
9
10
|
SAMPLED_PROBE_COUNT_INDEX,
|
|
@@ -23,16 +24,8 @@ require('./remote_config')
|
|
|
23
24
|
|
|
24
25
|
/** @typedef {import('node:inspector').Debugger.EvaluateOnCallFrameReturnType} EvaluateOnCallFrameResult */
|
|
25
26
|
|
|
26
|
-
const templateExpressionSetupCode =
|
|
27
|
-
|
|
28
|
-
const $dd_segmentInspectOptions = {
|
|
29
|
-
depth: 0,
|
|
30
|
-
customInspect: false,
|
|
31
|
-
maxArrayLength: 3,
|
|
32
|
-
maxStringLength: 8 * 1024,
|
|
33
|
-
breakLength: Infinity
|
|
34
|
-
};
|
|
35
|
-
`
|
|
27
|
+
const templateExpressionSetupCode = 'const $dd_inspectSegment = ' +
|
|
28
|
+
`globalThis[Symbol.for('dd-trace')][${JSON.stringify(INSPECT_SEGMENT_GLOBAL_PROPERTY)}];`
|
|
36
29
|
|
|
37
30
|
// Expression to run on a call frame of the paused thread to get its active trace and span id.
|
|
38
31
|
const getDDTagsExpression = `(() => {
|
|
@@ -7,7 +7,7 @@ const { Worker, MessageChannel, threadId: parentThreadId } = require('worker_thr
|
|
|
7
7
|
const log = require('../log')
|
|
8
8
|
const { fetchAgentInfo } = require('../agent/info')
|
|
9
9
|
const getDebuggerConfig = require('./config')
|
|
10
|
-
const { DEBUGGER_DIAGNOSTICS_V1, DEBUGGER_INPUT_V2 } = require('./constants')
|
|
10
|
+
const { DEBUGGER_DIAGNOSTICS_V1, DEBUGGER_INPUT_V2, INSPECT_SEGMENT_GLOBAL_PROPERTY } = require('./constants')
|
|
11
11
|
const { installProbeSampler, uninstallProbeSampler } = require('./probe_sampler')
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -64,7 +64,9 @@ function start (config, rcInstance) {
|
|
|
64
64
|
const logChannel = new MessageChannel()
|
|
65
65
|
configChannel = new MessageChannel()
|
|
66
66
|
|
|
67
|
-
globalThis[Symbol.for('dd-trace')]
|
|
67
|
+
const debuggerGlobals = globalThis[Symbol.for('dd-trace')]
|
|
68
|
+
debuggerGlobals.utilTypes = types
|
|
69
|
+
debuggerGlobals[INSPECT_SEGMENT_GLOBAL_PROPERTY] = require('./inspect-segment')
|
|
68
70
|
|
|
69
71
|
const probeSamplerBuffer = installProbeSampler()
|
|
70
72
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { inspect, types } = require('node:util')
|
|
4
|
+
|
|
5
|
+
const { NODE_MAJOR } = require('../../../../version')
|
|
6
|
+
|
|
7
|
+
/** @typedef {NonNullable<ReturnType<typeof globalThis.Object.getOwnPropertyDescriptor>>} PropertyDescriptor */
|
|
8
|
+
/** @typedef {Map<unknown, unknown> | Set<unknown>} Collection */
|
|
9
|
+
|
|
10
|
+
const mapEntries = Map.prototype.entries
|
|
11
|
+
const mapSet = Map.prototype.set
|
|
12
|
+
const mapSizeGetter = Object.getOwnPropertyDescriptor(Map.prototype, 'size').get
|
|
13
|
+
const setAdd = Set.prototype.add
|
|
14
|
+
const setSizeGetter = Object.getOwnPropertyDescriptor(Set.prototype, 'size').get
|
|
15
|
+
const setValues = Set.prototype.values
|
|
16
|
+
const mapIteratorNext = Object.getPrototypeOf(mapEntries.call(new Map())).next
|
|
17
|
+
const setIteratorNext = Object.getPrototypeOf(setValues.call(new Set())).next
|
|
18
|
+
|
|
19
|
+
const maxCollectionEntries = 3
|
|
20
|
+
const maxProperties = 5
|
|
21
|
+
const segmentInspectOptions = {
|
|
22
|
+
depth: 0,
|
|
23
|
+
customInspect: false,
|
|
24
|
+
maxArrayLength: maxCollectionEntries,
|
|
25
|
+
maxStringLength: 8 * 1024,
|
|
26
|
+
breakLength: Infinity,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = inspectSegment
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Inspect a dynamic-instrumentation template value without invoking user code.
|
|
33
|
+
* Unlike collections, `util.inspect` has no option for limiting the number of object properties, so this function
|
|
34
|
+
* truncates objects before inspecting them.
|
|
35
|
+
*
|
|
36
|
+
* @param {unknown} value
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function inspectSegment (value) {
|
|
40
|
+
if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
|
|
41
|
+
return inspect(value, segmentInspectOptions)
|
|
42
|
+
}
|
|
43
|
+
if (types.isProxy(value)) return '[Proxy]'
|
|
44
|
+
if (types.isMap(value)) return inspectCollection(value, true)
|
|
45
|
+
if (types.isSet(value)) return inspectCollection(value, false)
|
|
46
|
+
if (
|
|
47
|
+
Array.isArray(value) ||
|
|
48
|
+
types.isTypedArray(value) ||
|
|
49
|
+
types.isAnyArrayBuffer(value) ||
|
|
50
|
+
types.isDataView(value) ||
|
|
51
|
+
types.isWeakMap(value) ||
|
|
52
|
+
types.isWeakSet(value) ||
|
|
53
|
+
types.isMapIterator(value) ||
|
|
54
|
+
types.isSetIterator(value)
|
|
55
|
+
) {
|
|
56
|
+
return inspect(value, segmentInspectOptions)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @type {(string | symbol)[]} */
|
|
60
|
+
const keys = Object.keys(value)
|
|
61
|
+
let propertyCount = keys.length
|
|
62
|
+
const symbols = Object.getOwnPropertySymbols(value)
|
|
63
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
64
|
+
if (Object.getOwnPropertyDescriptor(value, symbols[i])?.enumerable === true) {
|
|
65
|
+
propertyCount++
|
|
66
|
+
if (keys.length < maxProperties) keys.push(symbols[i])
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (propertyCount <= maxProperties) {
|
|
71
|
+
// TODO: Decide whether allowing util.inspect to invoke Symbol.toStringTag getters is acceptable. If it is,
|
|
72
|
+
// remove inspectionCanRunUserCode and the related omission paths.
|
|
73
|
+
if (inspectionCanRunUserCode(value)) {
|
|
74
|
+
return '[Value omitted: inspection may execute user code]'
|
|
75
|
+
}
|
|
76
|
+
return inspect(value, segmentInspectOptions)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const truncated = {}
|
|
80
|
+
for (let i = 0; i < maxProperties; i++) {
|
|
81
|
+
const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(value, keys[i]))
|
|
82
|
+
if (
|
|
83
|
+
(keys[i] === Symbol.toStringTag && descriptor.get !== undefined) ||
|
|
84
|
+
(descriptor.value !== value && inspectionCanRunUserCode(descriptor.value))
|
|
85
|
+
) {
|
|
86
|
+
return '[Value omitted: inspection may execute user code]'
|
|
87
|
+
}
|
|
88
|
+
if (descriptor.value === value) descriptor.value = truncated
|
|
89
|
+
Object.defineProperty(truncated, keys[i], descriptor)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const omitted = propertyCount - maxProperties
|
|
93
|
+
const inspected = inspect(truncated, segmentInspectOptions)
|
|
94
|
+
return `${inspected.slice(0, -2)}, ... ${omitted} more ${omitted === 1 ? 'property' : 'properties'} }`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Inspect a Map or Set while bounding the number of entries on Node.js 18, where `util.inspect` does not.
|
|
99
|
+
*
|
|
100
|
+
* @param {Collection} value
|
|
101
|
+
* @param {boolean} isMap
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
function inspectCollection (value, isMap) {
|
|
105
|
+
if (NODE_MAJOR !== 18) return inspect(value, segmentInspectOptions)
|
|
106
|
+
|
|
107
|
+
const size = (isMap ? mapSizeGetter : setSizeGetter).call(value)
|
|
108
|
+
if (size <= maxCollectionEntries) return inspect(value, segmentInspectOptions)
|
|
109
|
+
|
|
110
|
+
const truncated = isMap ? new Map() : new Set()
|
|
111
|
+
const iterator = (isMap ? mapEntries : setValues).call(value)
|
|
112
|
+
const iteratorNext = isMap ? mapIteratorNext : setIteratorNext
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < maxCollectionEntries; i++) {
|
|
115
|
+
const result = iteratorNext.call(iterator)
|
|
116
|
+
if (result.done) break
|
|
117
|
+
|
|
118
|
+
if (isMap) {
|
|
119
|
+
const entry = result.value
|
|
120
|
+
const key = entry[0] === value ? truncated : entry[0]
|
|
121
|
+
const entryValue = entry[1] === value ? truncated : entry[1]
|
|
122
|
+
mapSet.call(truncated, key, entryValue)
|
|
123
|
+
} else {
|
|
124
|
+
const entryValue = result.value === value ? truncated : result.value
|
|
125
|
+
setAdd.call(truncated, entryValue)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const type = isMap ? 'Map' : 'Set'
|
|
130
|
+
const inspected = inspect(truncated, segmentInspectOptions)
|
|
131
|
+
const normalized = inspected.replace(`${type}(${maxCollectionEntries})`, `${type}(${size})`)
|
|
132
|
+
const remaining = size - maxCollectionEntries
|
|
133
|
+
return `${normalized.slice(0, -2)}, ... ${remaining} more item${remaining === 1 ? '' : 's'} }`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Determine whether inspecting a value could invoke a proxy trap or toStringTag getter.
|
|
138
|
+
*
|
|
139
|
+
* @param {unknown} value
|
|
140
|
+
* @returns {boolean}
|
|
141
|
+
*/
|
|
142
|
+
function inspectionCanRunUserCode (value) {
|
|
143
|
+
const type = typeof value
|
|
144
|
+
if (value === null || (type !== 'object' && type !== 'function')) return false
|
|
145
|
+
if (types.isProxy(value)) return true
|
|
146
|
+
|
|
147
|
+
let current = value
|
|
148
|
+
while (current !== null) {
|
|
149
|
+
if (Object.getOwnPropertyDescriptor(current, Symbol.toStringTag)?.get !== undefined) return true
|
|
150
|
+
current = Object.getPrototypeOf(current)
|
|
151
|
+
if (types.isProxy(current)) return true
|
|
152
|
+
}
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { format } = require('node:url')
|
|
4
|
+
|
|
5
|
+
const { HttpsProxyAgent } = require('../../../../vendor/dist/https-proxy-agent')
|
|
6
|
+
const { getProxyForUrl } = require('../../../../vendor/dist/proxy-from-env')
|
|
7
|
+
const log = require('../log')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} DirectEVPRoute
|
|
11
|
+
* @property {URL} url - Direct intake URL
|
|
12
|
+
* @property {string} basePath - Direct intake base path
|
|
13
|
+
* @property {object} headers - Direct intake authentication headers
|
|
14
|
+
* @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates an authenticated direct EVP intake route.
|
|
19
|
+
*
|
|
20
|
+
* This helper does not perform local receiver discovery.
|
|
21
|
+
*
|
|
22
|
+
* @param {import('../config/config-base')} config - Tracer configuration
|
|
23
|
+
* @param {string} intake - EVP intake subdomain
|
|
24
|
+
* @returns {DirectEVPRoute|undefined} Direct route when credentials and site are available
|
|
25
|
+
*/
|
|
26
|
+
function createDirectEVPRoute (config, intake) {
|
|
27
|
+
const apiKey = config.DD_API_KEY
|
|
28
|
+
if (!apiKey || !config.site) return
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const hostname = `${intake}.${config.site}`.toLowerCase()
|
|
32
|
+
const url = new URL(format({
|
|
33
|
+
protocol: 'https:',
|
|
34
|
+
hostname,
|
|
35
|
+
}))
|
|
36
|
+
if (
|
|
37
|
+
url.hostname !== hostname ||
|
|
38
|
+
url.username ||
|
|
39
|
+
url.password ||
|
|
40
|
+
url.port ||
|
|
41
|
+
url.pathname !== '/' ||
|
|
42
|
+
url.search ||
|
|
43
|
+
url.hash
|
|
44
|
+
) {
|
|
45
|
+
throw new Error('Invalid direct EVP intake URL')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const proxyUrl = getProxyForUrl(url.href)
|
|
49
|
+
const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
url,
|
|
53
|
+
basePath: '',
|
|
54
|
+
headers: {
|
|
55
|
+
'DD-API-KEY': apiKey,
|
|
56
|
+
},
|
|
57
|
+
...(agent && { agent }),
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
log.debug('Unable to configure direct EVP intake: %s', error.message)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { createDirectEVPRoute }
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { fetchAgentInfo } = require('../agent/info')
|
|
4
|
+
const log = require('../log')
|
|
5
|
+
|
|
6
|
+
const TRAILING_SLASHES = /\/+$/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Receiver discovery contract
|
|
10
|
+
*
|
|
11
|
+
* The tracer sends `GET /info` to its configured local Agent URL. An
|
|
12
|
+
* Agent-compatible trace receiver produces the response. The tracer does not.
|
|
13
|
+
*
|
|
14
|
+
* The full Agent and serverless-init embed the same trace receiver. A future
|
|
15
|
+
* in-process receiver can expose the same contract. Callers can therefore
|
|
16
|
+
* select capabilities without detecting the receiver implementation.
|
|
17
|
+
*
|
|
18
|
+
* For EVP discovery, `endpoints` advertises registered proxy paths.
|
|
19
|
+
* `evp_proxy_allowed_headers` advertises headers that the proxy forwards to
|
|
20
|
+
* intake. It does not include routing headers that the proxy consumes, such as
|
|
21
|
+
* `X-Datadog-EVP-Subdomain`.
|
|
22
|
+
*
|
|
23
|
+
* An advertised route is not a health check. The receiver can register an EVP
|
|
24
|
+
* route while configuration disables its handler. The request then returns
|
|
25
|
+
* `405`. The `/info` version also identifies the embedded Agent code, not a
|
|
26
|
+
* serverless-init image or deployment type.
|
|
27
|
+
*
|
|
28
|
+
* This module only discovers a candidate route. A missing or unresponsive
|
|
29
|
+
* `/info` endpoint returns an error through the shared request timeout and
|
|
30
|
+
* retry policy. A valid response without a compatible path returns no route.
|
|
31
|
+
* Discovery sends no events, so the caller can safely select direct intake
|
|
32
|
+
* after either result. The caller also owns later delivery failures. Exposure
|
|
33
|
+
* delivery uses retries and can therefore produce duplicates. After local
|
|
34
|
+
* retries fail, the caller can retry through direct intake and use that route
|
|
35
|
+
* for future batches.
|
|
36
|
+
*
|
|
37
|
+
* Reference implementations:
|
|
38
|
+
*
|
|
39
|
+
* Agent `/info` and EVP proxy:
|
|
40
|
+
* https://github.com/DataDog/datadog-agent/tree/main/pkg/trace/api
|
|
41
|
+
*
|
|
42
|
+
* serverless-init entry point and embedded trace receiver:
|
|
43
|
+
* https://github.com/DataDog/datadog-agent/blob/main/cmd/serverless-init/main.go
|
|
44
|
+
* https://github.com/DataDog/datadog-agent/blob/main/pkg/serverless/trace/trace.go
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Selects the first advertised EVP proxy path that the caller supports.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} agentInfo - Agent `/info` response
|
|
51
|
+
* @param {object} options - Selection options
|
|
52
|
+
* @param {string[]} options.supportedPaths - Supported paths in preference order
|
|
53
|
+
* @param {string[]} [options.requiredHeaders] - Headers that the proxy must forward unchanged to intake. Each
|
|
54
|
+
* header must appear in `evp_proxy_allowed_headers`. Do not include routing headers that the Agent consumes.
|
|
55
|
+
* @returns {string|undefined} Selected normalized path
|
|
56
|
+
*/
|
|
57
|
+
function selectEVPProxyPath (agentInfo, { supportedPaths, requiredHeaders = [] } = {}) {
|
|
58
|
+
if (!Array.isArray(agentInfo?.endpoints) ||
|
|
59
|
+
!Array.isArray(supportedPaths) ||
|
|
60
|
+
!Array.isArray(requiredHeaders) ||
|
|
61
|
+
requiredHeaders.some(header => typeof header !== 'string')) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const allowedHeaders = agentInfo.evp_proxy_allowed_headers
|
|
66
|
+
if (allowedHeaders !== undefined) {
|
|
67
|
+
if (!Array.isArray(allowedHeaders)) return
|
|
68
|
+
|
|
69
|
+
const normalizedHeaders = new Set()
|
|
70
|
+
for (const header of allowedHeaders) {
|
|
71
|
+
if (typeof header === 'string') {
|
|
72
|
+
normalizedHeaders.add(header.toLowerCase())
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (requiredHeaders.some(header => !normalizedHeaders.has(header.toLowerCase()))) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const advertisedPaths = new Set()
|
|
82
|
+
for (const endpoint of agentInfo.endpoints) {
|
|
83
|
+
if (typeof endpoint === 'string') {
|
|
84
|
+
advertisedPaths.add(endpoint.replace(TRAILING_SLASHES, ''))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const supportedPath of supportedPaths) {
|
|
89
|
+
if (typeof supportedPath !== 'string') continue
|
|
90
|
+
|
|
91
|
+
const normalizedPath = supportedPath.replace(TRAILING_SLASHES, '')
|
|
92
|
+
if (advertisedPaths.has(normalizedPath)) {
|
|
93
|
+
return normalizedPath
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Discovers an EVP proxy route through the configured Agent URL.
|
|
100
|
+
*
|
|
101
|
+
* This function performs discovery only when the caller invokes it. It stores
|
|
102
|
+
* no state. The Agent information client owns its existing response cache.
|
|
103
|
+
*
|
|
104
|
+
* @param {URL} url - Configured Agent URL
|
|
105
|
+
* @param {object} options - Selection options
|
|
106
|
+
* @param {string[]} options.supportedPaths - Supported paths in preference order
|
|
107
|
+
* @param {string[]} [options.requiredHeaders] - Headers that the proxy must forward unchanged to intake. Each
|
|
108
|
+
* header must appear in `evp_proxy_allowed_headers`. Do not include routing headers that the Agent consumes.
|
|
109
|
+
* @param {(error: Error|null, route?: {url: URL, basePath: string}) => void} callback - Result callback
|
|
110
|
+
* @returns {void}
|
|
111
|
+
*/
|
|
112
|
+
function discoverEVPProxy (url, options, callback) {
|
|
113
|
+
fetchAgentInfo(url, (error, agentInfo) => {
|
|
114
|
+
if (error) {
|
|
115
|
+
callback(error)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const basePath = selectEVPProxyPath(agentInfo, options)
|
|
120
|
+
if (basePath === undefined) {
|
|
121
|
+
callback(null)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
log.debug('EVP proxy route %s discovered through the configured local receiver', basePath)
|
|
126
|
+
callback(null, { url, basePath })
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
discoverEVPProxy,
|
|
132
|
+
selectEVPProxyPath,
|
|
133
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LEADING_SLASHES = /^\/+/
|
|
4
|
+
const TRAILING_SLASHES = /\/+$/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Joins a caller-supplied EVP proxy path and product endpoint.
|
|
8
|
+
*
|
|
9
|
+
* This utility does not perform EVP proxy discovery.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} basePath - EVP proxy base path
|
|
12
|
+
* @param {string} endpoint - Product intake endpoint
|
|
13
|
+
* @returns {string} Joined request path
|
|
14
|
+
*/
|
|
15
|
+
function joinEVPProxyPath (basePath, endpoint) {
|
|
16
|
+
const normalizedBasePath = basePath.replace(TRAILING_SLASHES, '')
|
|
17
|
+
const normalizedEndpoint = endpoint.replace(LEADING_SLASHES, '')
|
|
18
|
+
|
|
19
|
+
return `${normalizedBasePath}/${normalizedEndpoint}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { joinEVPProxyPath }
|
|
@@ -93,7 +93,10 @@ function request (data, options, callback) {
|
|
|
93
93
|
|
|
94
94
|
docker.inject(options.headers)
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
const connectionOptions = {
|
|
97
|
+
...options,
|
|
98
|
+
agent: options.agent ?? (isSecure ? httpsAgent : httpAgent),
|
|
99
|
+
}
|
|
97
100
|
|
|
98
101
|
/**
|
|
99
102
|
* @param {import('node:http').IncomingMessage} res
|
|
@@ -218,7 +221,7 @@ function request (data, options, callback) {
|
|
|
218
221
|
}
|
|
219
222
|
}
|
|
220
223
|
|
|
221
|
-
const req = client.request(
|
|
224
|
+
const req = client.request(connectionOptions, (res) => onResponse(res, complete, handleError))
|
|
222
225
|
|
|
223
226
|
req.once('close', finalize)
|
|
224
227
|
req.once('timeout', finalize)
|
|
@@ -33,7 +33,8 @@ function datasetRecordFromResource (resource) {
|
|
|
33
33
|
attrs.input ?? null,
|
|
34
34
|
attrs.expected_output ?? null,
|
|
35
35
|
attrs.metadata ?? {},
|
|
36
|
-
id
|
|
36
|
+
id,
|
|
37
|
+
attrs.tags ?? []
|
|
37
38
|
)
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -195,7 +196,7 @@ class ExperimentsClient {
|
|
|
195
196
|
const resources = Array.isArray(response?.records)
|
|
196
197
|
? response.records
|
|
197
198
|
: (Array.isArray(response?.data) ? response.data : [])
|
|
198
|
-
return resources
|
|
199
|
+
return datasetMutationResultFromResources(resources)
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
async batchUpdateDatasetRecords (projectId, datasetId, attributes) {
|
|
@@ -226,6 +227,9 @@ class ExperimentsClient {
|
|
|
226
227
|
const query = new URLSearchParams()
|
|
227
228
|
if (options.cursor) query.set('page[cursor]', options.cursor)
|
|
228
229
|
if (options.version !== undefined && options.version !== null) query.set('filter[version]', String(options.version))
|
|
230
|
+
if (Array.isArray(options.tags)) {
|
|
231
|
+
for (const tag of options.tags) query.append('filter[tags]', tag)
|
|
232
|
+
}
|
|
229
233
|
const response = await this.request(
|
|
230
234
|
'GET',
|
|
231
235
|
`${API_BASE_PATH}/${projectId}/datasets/${datasetId}/records?${query.toString()}`
|