dd-trace 6.10.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.
Files changed (173) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/README.md +0 -17
  3. package/index.d.ts +128 -6
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +27 -23
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/cucumber.js +30 -15
  13. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  14. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  15. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  16. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  17. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  18. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  19. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  20. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  21. package/packages/datadog-instrumentations/src/jest.js +25 -17
  22. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  23. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +135 -19
  25. package/packages/datadog-instrumentations/src/playwright-reporter.js +247 -0
  26. package/packages/datadog-instrumentations/src/playwright.js +182 -35
  27. package/packages/datadog-instrumentations/src/router.js +2 -0
  28. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +6 -8
  29. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  30. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  31. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  32. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  33. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  34. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  35. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  36. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  37. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  38. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  39. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  40. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  41. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  42. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  43. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  44. package/packages/datadog-plugin-electron/src/index.js +2 -0
  45. package/packages/datadog-plugin-fs/src/index.js +1 -1
  46. package/packages/datadog-plugin-jest/src/index.js +6 -0
  47. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  48. package/packages/datadog-plugin-nats/src/index.js +1 -1
  49. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  50. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  51. package/packages/datadog-plugin-playwright/src/index.js +46 -13
  52. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  53. package/packages/datadog-webpack/index.js +0 -11
  54. package/packages/dd-trace/index.js +0 -1
  55. package/packages/dd-trace/src/agent/info.js +6 -5
  56. package/packages/dd-trace/src/aiguard/client.js +100 -1
  57. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  58. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  59. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  60. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  61. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  62. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  63. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  64. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  65. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  67. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  68. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  69. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  70. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  71. package/packages/dd-trace/src/carrier.js +107 -48
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  73. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  74. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +29 -3
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +27 -9
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  78. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +26 -8
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  80. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  81. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  84. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -4
  85. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  86. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  87. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  88. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  89. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +10 -4
  90. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  91. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  92. package/packages/dd-trace/src/config/index.js +13 -2
  93. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  94. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  95. package/packages/dd-trace/src/datastreams/index.js +2 -0
  96. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  97. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  98. package/packages/dd-trace/src/datastreams/size.js +5 -0
  99. package/packages/dd-trace/src/debugger/constants.js +1 -0
  100. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  101. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -11
  102. package/packages/dd-trace/src/debugger/index.js +4 -2
  103. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  104. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  105. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  106. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  107. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  108. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  109. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  110. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  111. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  112. package/packages/dd-trace/src/llmobs/experiments/client.js +167 -5
  113. package/packages/dd-trace/src/llmobs/experiments/dataset.js +484 -92
  114. package/packages/dd-trace/src/llmobs/experiments/experiment.js +302 -29
  115. package/packages/dd-trace/src/llmobs/experiments/index.js +75 -64
  116. package/packages/dd-trace/src/llmobs/experiments/noop.js +135 -7
  117. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  118. package/packages/dd-trace/src/llmobs/experiments/util.js +101 -9
  119. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  120. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  121. package/packages/dd-trace/src/noop/proxy.js +3 -4
  122. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  123. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  124. package/packages/dd-trace/src/openfeature/index.js +7 -4
  125. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  126. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  127. package/packages/dd-trace/src/openfeature/writers/exposures.js +62 -14
  128. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  129. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  130. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  131. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  132. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  133. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  135. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  136. package/packages/dd-trace/src/opentracing/propagation/text_map.js +22 -13
  137. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  138. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  139. package/packages/dd-trace/src/plugin_manager.js +3 -3
  140. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  141. package/packages/dd-trace/src/plugins/index.js +1 -0
  142. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  143. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  144. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  145. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  146. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  148. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  149. package/packages/dd-trace/src/proxy.js +38 -29
  150. package/packages/dd-trace/src/ritm.js +9 -2
  151. package/packages/dd-trace/src/serverless.js +37 -0
  152. package/packages/dd-trace/src/span_format.js +1 -0
  153. package/packages/dd-trace/src/span_stats.js +30 -3
  154. package/packages/dd-trace/src/storage-channels.js +86 -0
  155. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  156. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  157. package/packages/dd-trace/src/tracer.js +9 -2
  158. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  159. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  160. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  161. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  162. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  163. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  164. package/vendor/dist/https-proxy-agent/index.js +4 -0
  165. package/vendor/dist/protobufjs/index.js +1 -1
  166. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  167. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  168. package/vendor/dist/proxy-from-env/index.js +1 -0
  169. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  170. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  171. package/packages/dd-trace/src/feature-registry.js +0 -29
  172. package/packages/dd-trace/src/openfeature/register.js +0 -35
  173. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -24,23 +24,25 @@ class BufferingExporter {
24
24
  this._export(trace)
25
25
  }
26
26
 
27
- _export (payload, writer = this._writer, timerKey = '_timer') {
27
+ _export (payload, writer = this._writer, timerKey = '_timer', deferImmediateFlush = false) {
28
28
  if (this._config.isCiVisibility) {
29
29
  incrementCountMetric(TELEMETRY_EVENTS_ENQUEUED_FOR_SERIALIZATION, {}, payload.length)
30
30
  }
31
- writer.append(payload)
31
+ const appended = writer.append(payload)
32
32
 
33
33
  const { flushInterval } = this._config
34
34
 
35
- if (flushInterval === 0) {
35
+ if (flushInterval === 0 && !deferImmediateFlush) {
36
36
  writer.flush()
37
- } else if (this[timerKey] === undefined) {
37
+ } else if (flushInterval !== 0 && this[timerKey] === undefined) {
38
38
  this[timerKey] = setTimeout(() => {
39
39
  writer.flush()
40
40
  this[timerKey] = undefined
41
41
  }, flushInterval)
42
42
  this[timerKey].unref?.()
43
43
  }
44
+
45
+ return appended
44
46
  }
45
47
 
46
48
  getUncodedTraces () {
@@ -93,7 +93,10 @@ function request (data, options, callback) {
93
93
 
94
94
  docker.inject(options.headers)
95
95
 
96
- options.agent = isSecure ? httpsAgent : httpAgent
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(options, (res) => onResponse(res, complete, handleError))
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)
@@ -17,10 +17,10 @@ class Writer {
17
17
 
18
18
  #isFirstFlush = true
19
19
 
20
- flush (done = () => {}) {
20
+ flush (done = () => {}, options) {
21
21
  const count = this._encoder.count()
22
22
 
23
- if (!request.writable) {
23
+ if (!request.writable && options?.deadline === undefined) {
24
24
  this._encoder.reset()
25
25
  done()
26
26
  } else if (count > 0) {
@@ -44,23 +44,28 @@ class Writer {
44
44
  done()
45
45
  return
46
46
  }
47
- this._sendPayload(payload, count, done)
47
+ if (options === undefined) {
48
+ this._sendPayload(payload, count, done)
49
+ } else {
50
+ this._sendPayload(payload, count, done, options)
51
+ }
48
52
  } else {
49
53
  done()
50
54
  }
51
55
  }
52
56
 
53
- append (payload) {
54
- if (!request.writable) {
57
+ append (payload, options) {
58
+ if (!request.writable && options?.deadline === undefined) {
55
59
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
56
60
  log.debug(() => `Maximum number of active requests reached. Payload discarded: ${safeJSONStringify(payload)}`)
57
- return
61
+ return false
58
62
  }
59
63
 
60
64
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
61
65
  log.debug(() => `Encoding payload: ${safeJSONStringify(payload)}`)
62
66
 
63
67
  this._encode(payload)
68
+ return true
64
69
  }
65
70
 
66
71
  _encode (payload) {
@@ -3,6 +3,9 @@
3
3
  // Control-plane HTTP client for LLM Obs Experiments. Uses the global `fetch`,
4
4
  // so this module adds no new dependency; credentials and site come from config.
5
5
 
6
+ const { Dataset, DatasetRecord } = require('./dataset')
7
+ const { ExperimentResult } = require('./result')
8
+
6
9
  const API_BASE_PATH = '/api/v2/llm-obs/v1'
7
10
 
8
11
  // Control-plane host for a Datadog site, e.g.
@@ -22,12 +25,68 @@ function appHost (site) {
22
25
  return site.split('.').length === 2 ? `app.${site}` : site
23
26
  }
24
27
 
28
+ function datasetRecordFromResource (resource) {
29
+ const attrs = resource?.attributes ?? resource ?? {}
30
+ const id = String(resource?.id ?? attrs.id ?? '')
31
+ if (id === '') throw new Error('Dataset record response is missing an id')
32
+ return new DatasetRecord(
33
+ attrs.input ?? null,
34
+ attrs.expected_output ?? null,
35
+ attrs.metadata ?? {},
36
+ id,
37
+ attrs.tags ?? []
38
+ )
39
+ }
40
+
41
+ function datasetVersionFromResource (resource) {
42
+ const attrs = resource?.attributes ?? resource ?? {}
43
+ return attrs.valid_from_version ?? attrs.version ?? null
44
+ }
45
+
46
+ function datasetVersionFromResources (resources) {
47
+ const versions = resources
48
+ .map(datasetVersionFromResource)
49
+ .filter(version => version != null)
50
+ .map(Number)
51
+ .filter(Number.isFinite)
52
+ if (versions.length === 0) return null
53
+ return Math.max(...versions)
54
+ }
55
+
56
+ function datasetMutationResultFromResources (resources) {
57
+ return {
58
+ records: resources.map(datasetRecordFromResource),
59
+ version: datasetVersionFromResources(resources),
60
+ }
61
+ }
62
+
63
+ function datasetFromResource (client, projectId, resource) {
64
+ const attrs = resource?.attributes ?? resource ?? {}
65
+ const version = attrs.current_version ?? null
66
+ return Dataset.fromExisting(
67
+ client,
68
+ String(attrs.name ?? ''),
69
+ String(attrs.description ?? ''),
70
+ resource?.id ?? attrs.id ?? null,
71
+ projectId,
72
+ [],
73
+ version,
74
+ version
75
+ )
76
+ }
77
+
78
+ function experimentFromResource (client, resource) {
79
+ const id = resource?.id
80
+ return new ExperimentResult(id, [], id == null ? null : `${client.appBase}/llm/experiments/${id}`)
81
+ }
82
+
25
83
  class ExperimentsClient {
26
84
  #apiKey
27
85
  #appKey
28
86
  #site
29
87
  #projectName
30
88
  #timeout
89
+ apiBase
31
90
  #cachedProjectId
32
91
 
33
92
  constructor ({ apiKey, appKey, site, projectName, timeout = 30_000 } = {}) {
@@ -36,6 +95,7 @@ class ExperimentsClient {
36
95
  this.#site = site
37
96
  this.#projectName = projectName
38
97
  this.#timeout = timeout
98
+ this.apiBase = `https://${apiHost(this.#site)}`
39
99
  this.#cachedProjectId = null
40
100
  }
41
101
 
@@ -61,7 +121,7 @@ class ExperimentsClient {
61
121
  // Low-level request. Builds https://api.<site><path>, attaches both keys, and
62
122
  // returns the parsed JSON body. Throws with status + body on a non-2xx.
63
123
  async request (method, path, body) {
64
- const url = `https://${apiHost(this.#site)}${path}`
124
+ const url = `${this.apiBase}${path}`
65
125
  const headers = {
66
126
  'DD-API-KEY': this.#apiKey,
67
127
  'DD-APPLICATION-KEY': this.#appKey,
@@ -92,6 +152,110 @@ class ExperimentsClient {
92
152
  return text ? JSON.parse(text) : {}
93
153
  }
94
154
 
155
+ jsonApiRequest (method, path, type, attributes) {
156
+ return this.request(method, path, {
157
+ data: { type, attributes },
158
+ })
159
+ }
160
+
161
+ async createProject (name) {
162
+ const response = await this.jsonApiRequest('POST', `${API_BASE_PATH}/projects`, 'projects', { name })
163
+ return response?.data ?? null
164
+ }
165
+
166
+ async createDataset (projectId, attributes) {
167
+ const response = await this.jsonApiRequest('POST', `${API_BASE_PATH}/${projectId}/datasets`, 'datasets', attributes)
168
+ return datasetFromResource(this, projectId, response?.data ?? null)
169
+ }
170
+
171
+ deleteDataset (projectId, datasetId) {
172
+ return this.jsonApiRequest('POST', `${API_BASE_PATH}/${projectId}/datasets/delete`, 'datasets', {
173
+ type: 'soft',
174
+ dataset_ids: [datasetId],
175
+ })
176
+ }
177
+
178
+ async listDatasets (projectId, options = {}) {
179
+ const query = new URLSearchParams()
180
+ if (options.name !== undefined) query.set('filter[name]', options.name)
181
+ const response = await this.request('GET', `${API_BASE_PATH}/${projectId}/datasets?${query.toString()}`)
182
+ const resources = Array.isArray(response?.data) ? response.data : []
183
+ return resources.map(resource => datasetFromResource(this, projectId, resource))
184
+ }
185
+
186
+ async appendDatasetRecords (projectId, datasetId, records) {
187
+ const response = await this.jsonApiRequest(
188
+ 'POST',
189
+ `${API_BASE_PATH}/${projectId}/datasets/${datasetId}/records`,
190
+ 'datasets',
191
+ { records }
192
+ )
193
+ // The append-records response has used both a top-level `records` array
194
+ // and JSON:API `data` resources. Accept either so generated/custom record
195
+ // ids are preserved for experiment row tagging.
196
+ const resources = Array.isArray(response?.records)
197
+ ? response.records
198
+ : (Array.isArray(response?.data) ? response.data : [])
199
+ return datasetMutationResultFromResources(resources)
200
+ }
201
+
202
+ async batchUpdateDatasetRecords (projectId, datasetId, attributes) {
203
+ const response = await this.request(
204
+ 'POST',
205
+ `${API_BASE_PATH}/${projectId}/datasets/${datasetId}/batch_update`,
206
+ {
207
+ data: {
208
+ type: 'datasets',
209
+ id: datasetId,
210
+ attributes: {
211
+ insert_records: attributes.insert_records ?? [],
212
+ update_records: attributes.update_records ?? [],
213
+ delete_records: attributes.delete_records ?? [],
214
+ deduplicate: attributes.deduplicate !== false,
215
+ create_new_version: attributes.create_new_version !== false,
216
+ },
217
+ },
218
+ }
219
+ )
220
+ const resources = Array.isArray(response?.records)
221
+ ? response.records
222
+ : (Array.isArray(response?.data) ? response.data : [])
223
+ return datasetMutationResultFromResources(resources)
224
+ }
225
+
226
+ async listDatasetRecords (projectId, datasetId, options = {}) {
227
+ const query = new URLSearchParams()
228
+ if (options.cursor) query.set('page[cursor]', options.cursor)
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
+ }
233
+ const response = await this.request(
234
+ 'GET',
235
+ `${API_BASE_PATH}/${projectId}/datasets/${datasetId}/records?${query.toString()}`
236
+ )
237
+ const records = Array.isArray(response?.data) ? response.data.map(datasetRecordFromResource) : []
238
+ return { records, after: response?.meta?.after ?? '' }
239
+ }
240
+
241
+ async createExperiment (attributes) {
242
+ const response = await this.jsonApiRequest('POST', `${API_BASE_PATH}/experiments`, 'experiments', attributes)
243
+ return experimentFromResource(this, response?.data ?? null)
244
+ }
245
+
246
+ postExperimentEvents (experimentId, attributes) {
247
+ return this.jsonApiRequest(
248
+ 'POST',
249
+ `${API_BASE_PATH}/experiments/${experimentId}/events`,
250
+ 'experiments',
251
+ attributes
252
+ )
253
+ }
254
+
255
+ updateExperiment (experimentId, attributes) {
256
+ return this.jsonApiRequest('PATCH', `${API_BASE_PATH}/experiments/${experimentId}`, 'experiments', attributes)
257
+ }
258
+
95
259
  // Resolve the project id for `name`, creating it if absent. The create
96
260
  // endpoint is get-or-create on name, so repeated calls return the same id.
97
261
  // Cached after the first resolution.
@@ -100,14 +264,12 @@ class ExperimentsClient {
100
264
 
101
265
  let response
102
266
  try {
103
- response = await this.request('POST', `${API_BASE_PATH}/projects`, {
104
- data: { type: 'projects', attributes: { name } },
105
- })
267
+ response = await this.createProject(name)
106
268
  } catch (err) {
107
269
  throw new Error(`Failed to create or get project '${name}': ${err.message}`)
108
270
  }
109
271
 
110
- this.#cachedProjectId = response?.data?.id ?? null
272
+ this.#cachedProjectId = response?.id ?? null
111
273
  return this.#cachedProjectId
112
274
  }
113
275
  }