dd-trace 6.6.0 → 6.8.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 (209) hide show
  1. package/ci/diagnose.js +162 -21
  2. package/ci/init.js +0 -1
  3. package/ci/runbook.md +187 -180
  4. package/ci/test-optimization-validation/approval-artifacts.js +3 -1
  5. package/ci/test-optimization-validation/approval.js +52 -15
  6. package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
  7. package/ci/test-optimization-validation/ci-discovery.js +1 -1
  8. package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
  9. package/ci/test-optimization-validation/ci-remediation.js +112 -39
  10. package/ci/test-optimization-validation/cli.js +516 -681
  11. package/ci/test-optimization-validation/command-blocker.js +188 -33
  12. package/ci/test-optimization-validation/command-output-policy.js +8 -31
  13. package/ci/test-optimization-validation/command-runner.js +189 -245
  14. package/ci/test-optimization-validation/environment.js +90 -0
  15. package/ci/test-optimization-validation/executable.js +159 -388
  16. package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
  17. package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
  18. package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
  19. package/ci/test-optimization-validation/generated-files.js +84 -22
  20. package/ci/test-optimization-validation/generated-test-contract.js +281 -0
  21. package/ci/test-optimization-validation/generated-verifier.js +49 -16
  22. package/ci/test-optimization-validation/literal-environment.js +57 -0
  23. package/ci/test-optimization-validation/manifest-loader.js +2 -22
  24. package/ci/test-optimization-validation/manifest-scaffold.js +815 -515
  25. package/ci/test-optimization-validation/manifest-schema.js +494 -702
  26. package/ci/test-optimization-validation/offline-fixtures.js +4 -1
  27. package/ci/test-optimization-validation/offline-output.js +6 -6
  28. package/ci/test-optimization-validation/plan-writer.js +243 -984
  29. package/ci/test-optimization-validation/preflight-runner.js +49 -55
  30. package/ci/test-optimization-validation/redaction.js +2 -1
  31. package/ci/test-optimization-validation/report-writer.js +391 -1357
  32. package/ci/test-optimization-validation/result-semantics.js +86 -0
  33. package/ci/test-optimization-validation/runner-command.js +249 -0
  34. package/ci/test-optimization-validation/runner-contract.js +664 -0
  35. package/ci/test-optimization-validation/safe-files.js +4 -4
  36. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
  39. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
  40. package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
  41. package/ci/test-optimization-validation/scenarios/test-management.js +6 -5
  42. package/ci/test-optimization-validation/source-text.js +87 -0
  43. package/ci/test-optimization-validation/test-output.js +12 -22
  44. package/ci/vitest-no-worker-init-setup.mjs +348 -51
  45. package/ext/tags.d.ts +1 -0
  46. package/index.d.ts +84 -13
  47. package/package.json +10 -4
  48. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  49. package/packages/datadog-esbuild/index.js +4 -2
  50. package/packages/datadog-esbuild/src/utils.js +1 -1
  51. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
  52. package/packages/datadog-instrumentations/src/cucumber.js +9 -6
  53. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  54. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  55. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  56. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  57. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
  58. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  59. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
  60. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
  61. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  62. package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
  63. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  64. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  65. package/packages/datadog-instrumentations/src/jest.js +8 -14
  66. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  67. package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
  68. package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
  69. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
  70. package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
  71. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  72. package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
  73. package/packages/datadog-instrumentations/src/playwright.js +3 -2
  74. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  75. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
  76. package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
  77. package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
  78. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  79. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  80. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  81. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  82. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  83. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  84. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  85. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  86. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  87. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
  88. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  89. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  90. package/packages/datadog-plugin-fs/src/index.js +0 -4
  91. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  92. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  93. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  94. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  95. package/packages/datadog-plugin-http/src/client.js +2 -1
  96. package/packages/datadog-plugin-jest/src/index.js +2 -3
  97. package/packages/datadog-plugin-jest/src/util.js +2 -1
  98. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  99. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  100. package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
  101. package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
  102. package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
  103. package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
  104. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  105. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  106. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  107. package/packages/datadog-plugin-undici/src/index.js +2 -1
  108. package/packages/datadog-plugin-vitest/src/index.js +84 -11
  109. package/packages/datadog-webpack/index.js +2 -2
  110. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  111. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  112. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  113. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  114. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  115. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  116. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  117. package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
  118. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +50 -51
  121. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
  122. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  124. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  125. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
  126. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  127. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +7 -5
  128. package/packages/dd-trace/src/config/defaults.js +14 -6
  129. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
  130. package/packages/dd-trace/src/config/index.js +15 -0
  131. package/packages/dd-trace/src/config/parsers.js +19 -1
  132. package/packages/dd-trace/src/config/supported-configurations.json +12 -6
  133. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  134. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  135. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  136. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  137. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  138. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  139. package/packages/dd-trace/src/encode/span-stats.js +7 -1
  140. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  141. package/packages/dd-trace/src/exporter.js +16 -9
  142. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  143. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  144. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  145. package/packages/dd-trace/src/flare/index.js +2 -2
  146. package/packages/dd-trace/src/id.js +1 -1
  147. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  148. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  149. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  150. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  151. package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
  152. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  153. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  154. package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
  155. package/packages/dd-trace/src/llmobs/index.js +9 -1
  156. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
  157. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  158. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  159. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  160. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  161. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
  162. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
  163. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
  164. package/packages/dd-trace/src/llmobs/sdk.js +6 -12
  165. package/packages/dd-trace/src/llmobs/span_processor.js +14 -7
  166. package/packages/dd-trace/src/llmobs/tagger.js +24 -5
  167. package/packages/dd-trace/src/llmobs/util.js +56 -3
  168. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  169. package/packages/dd-trace/src/noop/proxy.js +2 -2
  170. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
  171. package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
  172. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
  173. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  174. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
  175. package/packages/dd-trace/src/opentracing/span.js +1 -1
  176. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  177. package/packages/dd-trace/src/plugins/ci_plugin.js +114 -23
  178. package/packages/dd-trace/src/plugins/index.js +2 -0
  179. package/packages/dd-trace/src/plugins/outbound.js +4 -1
  180. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  181. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  182. package/packages/dd-trace/src/plugins/util/ci.js +7 -6
  183. package/packages/dd-trace/src/plugins/util/git.js +1 -1
  184. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
  185. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  186. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  187. package/packages/dd-trace/src/plugins/util/test.js +38 -12
  188. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  189. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  190. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  191. package/packages/dd-trace/src/priority_sampler.js +6 -3
  192. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  193. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  194. package/packages/dd-trace/src/ritm.js +5 -20
  195. package/packages/dd-trace/src/span_processor.js +1 -1
  196. package/packages/dd-trace/src/span_stats.js +22 -4
  197. package/packages/dd-trace/src/startup-log.js +3 -0
  198. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  199. package/packages/dd-trace/src/util.js +16 -0
  200. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  201. package/vendor/dist/pprof-format/index.js +1 -1
  202. package/vendor/dist/source-map/index.js +1 -1
  203. package/ci/test-optimization-validation/command-suitability.js +0 -471
  204. package/ci/test-optimization-validation/init-probe-preload.js +0 -163
  205. package/ci/test-optimization-validation/init-probe.js +0 -246
  206. package/ci/test-optimization-validation/late-initialization.js +0 -63
  207. package/ci/test-optimization-validation/local-command.js +0 -163
  208. package/ci/test-optimization-validation/setup-runner.js +0 -97
  209. package/ci/test-optimization-validation-manifest.schema.json +0 -1
@@ -32,11 +32,9 @@ function parseUrl (urlObjOrString) {
32
32
  // `urlToHttpOptions` returns `pathname` at runtime, but @types/node narrows it
33
33
  // to `ClientRequestArgs`, which omits it; cast so the named-pipe fold below can
34
34
  // read and rewrite it.
35
- const url = /** @type {import('node:http').ClientRequestArgs & { pathname: string }} */ (
36
- urlObjOrString !== null && typeof urlObjOrString === 'object'
37
- ? urlToHttpOptions(urlObjOrString)
38
- : urlToHttpOptions(new URL(urlObjOrString))
39
- )
35
+ const url = /** @type {import('node:http').ClientRequestArgs & { pathname: string }} */ (urlToHttpOptions(
36
+ urlObjOrString !== null && typeof urlObjOrString === 'object' ? urlObjOrString : new URL(urlObjOrString)
37
+ ))
40
38
 
41
39
  if (url.protocol === 'unix:' && url.hostname === '.') {
42
40
  url.path = url.pathname = `//.${url.pathname}`
@@ -16,6 +16,8 @@ class ElectronExporter {
16
16
  }
17
17
 
18
18
  export (spans) {
19
+ if (!traceChannel.hasSubscribers) return
20
+
19
21
  this.#traces.push(spans)
20
22
 
21
23
  const { flushInterval } = this._config
@@ -35,7 +37,8 @@ class ElectronExporter {
35
37
  clearTimeout(this.#timer)
36
38
  this.#timer = undefined
37
39
 
38
- const traces = this.#traces.splice(0)
40
+ const traces = this.#traces
41
+ this.#traces = []
39
42
 
40
43
  if (traces.length > 0 && traceChannel.hasSubscribers) {
41
44
  const formattedTraces = traces.map(spans => spans.map(span => normalizeSpan(truncateSpan(span))))
@@ -78,7 +78,6 @@ class ExternalLogger {
78
78
 
79
79
  // Flushes logs with optional callback for when the call is complete
80
80
  flush (cb = () => {}) {
81
- let logs
82
81
  let numLogs
83
82
  let encodedLogs
84
83
 
@@ -88,7 +87,7 @@ class ExternalLogger {
88
87
  }
89
88
 
90
89
  try {
91
- logs = this.queue
90
+ const logs = this.queue
92
91
  this.queue = []
93
92
 
94
93
  numLogs = logs.length
@@ -39,8 +39,8 @@ const flare = {
39
39
  logChannel?.unsubscribe(logger)
40
40
  logChannel = new LogChannel(logLevel)
41
41
  logChannel.subscribe(logger)
42
- tracerLogs = tracerLogs || new FlareFile()
43
- timer = timer || setTimeout(flare.cleanup, TIMEOUT)
42
+ tracerLogs ||= new FlareFile()
43
+ timer ||= setTimeout(flare.cleanup, TIMEOUT)
44
44
  },
45
45
 
46
46
  send (task) {
@@ -185,7 +185,7 @@ function toNumberString (buffer, radix) {
185
185
  let low = readInt32(buffer, buffer.length - 4)
186
186
  let str = ''
187
187
 
188
- radix = radix || 10
188
+ radix ||= 10
189
189
 
190
190
  while (1) {
191
191
  const mod = (high % radix) * UINT_MAX + low
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
- SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval'],
4
+ SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval', 'experiment'],
5
5
  SPAN_KIND: '_ml_obs.meta.span.kind',
6
6
  SESSION_ID: '_ml_obs.session_id',
7
7
  SESSION_ID_TRACE_DEFAULT_KEY: '_ml_obs.trace_session_id',
@@ -14,9 +14,11 @@ function apiHost (site) {
14
14
  }
15
15
 
16
16
  // Web-app host for dashboard URLs. Single-level sites (datadoghq.com,
17
- // ddog-gov.com) are served from the `app.` subdomain; regional sites
18
- // (us3.datadoghq.com, ap1.datadoghq.com) are used as-is.
17
+ // ddog-gov.com) are served from the `app.` subdomain; staging uses
18
+ // dd.datad0g.com; regional sites (us3.datadoghq.com, ap1.datadoghq.com)
19
+ // are used as-is.
19
20
  function appHost (site) {
21
+ if (site === 'datad0g.com') return 'dd.datad0g.com'
20
22
  return site.split('.').length === 2 ? `app.${site}` : site
21
23
  }
22
24
 
@@ -2,15 +2,35 @@
2
2
 
3
3
  const { API_BASE_PATH } = require('./client')
4
4
 
5
- // Immutable dataset record: { input, expectedOutput?, metadata? }.
5
+ // Dataset record: { input, expectedOutput?, metadata?, id? }.
6
+ // `id` may be user-provided before push or filled from the backend-created record.
6
7
  class DatasetRecord {
7
- constructor (input, expectedOutput = null, metadata = {}) {
8
+ constructor (input, expectedOutput = null, metadata = {}, id = null) {
8
9
  this.input = input
9
10
  this.expectedOutput = expectedOutput ?? null
10
11
  this.metadata = metadata ?? {}
12
+ this.id = id ?? null
11
13
  }
12
14
  }
13
15
 
16
+ function createdRecordsFromResponse (response) {
17
+ if (Array.isArray(response?.records)) return response.records
18
+ if (Array.isArray(response?.data)) return response.data
19
+ return []
20
+ }
21
+
22
+ function recordIdFromCreatedRecord (record) {
23
+ return String(record?.id ?? record?.attributes?.id ?? '')
24
+ }
25
+
26
+ function versionFromCreatedRecords (records) {
27
+ const versions = records
28
+ .map(record => Number(record?.attributes?.valid_from_version ?? record?.attributes?.version))
29
+ .filter(Number.isFinite)
30
+ if (versions.length === 0) return null
31
+ return Math.max(...versions)
32
+ }
33
+
14
34
  // A local buffer of dataset records, created remotely and pushed on first run
15
35
  // (or eagerly via push()). Pushes are incremental.
16
36
  class Dataset {
@@ -22,6 +42,8 @@ class Dataset {
22
42
  #id
23
43
  #projectId
24
44
  #pushedCount
45
+ #version
46
+ #latestVersion
25
47
 
26
48
  constructor (client, name, description = '') {
27
49
  this.#client = client
@@ -32,16 +54,20 @@ class Dataset {
32
54
  this.#id = null
33
55
  this.#projectId = null
34
56
  this.#pushedCount = 0
57
+ this.#version = null
58
+ this.#latestVersion = null
35
59
  }
36
60
 
37
61
  // Build a Dataset that already exists remotely (used by pullDataset).
38
- static fromExisting (client, name, description, id, projectId, records, recordIds) {
62
+ static fromExisting (client, name, description, id, projectId, records, recordIds, version, latestVersion) {
39
63
  const dataset = new Dataset(client, name, description)
40
64
  dataset.#id = id
41
65
  dataset.#projectId = projectId
42
66
  dataset.#records.push(...records)
43
67
  dataset.#recordIds.push(...recordIds)
44
68
  dataset.#pushedCount = records.length
69
+ dataset.#version = version ?? null
70
+ dataset.#latestVersion = latestVersion ?? version ?? null
45
71
  return dataset
46
72
  }
47
73
 
@@ -74,6 +100,14 @@ class Dataset {
74
100
  return this.#projectId
75
101
  }
76
102
 
103
+ version () {
104
+ return this.#version
105
+ }
106
+
107
+ latestVersion () {
108
+ return this.#latestVersion
109
+ }
110
+
77
111
  // Dashboard URL for this dataset, or null until pushed/pulled.
78
112
  url () {
79
113
  if (this.#id === null) return null
@@ -100,7 +134,12 @@ class Dataset {
100
134
  throw new Error(`Failed to create dataset '${this.#name}': ${err.message}`)
101
135
  }
102
136
  this.#id = response?.data?.id ?? null
137
+ if (this.#id === null) {
138
+ throw new Error(`Failed to create dataset '${this.#name}': backend response is missing dataset id`)
139
+ }
103
140
  this.#projectId = projectId
141
+ this.#version = response?.data?.attributes?.current_version ?? this.#version
142
+ this.#latestVersion = response?.data?.attributes?.current_version ?? this.#latestVersion
104
143
  }
105
144
 
106
145
  if (this.#pushedCount >= this.#records.length) return { pushedCount: 0, totalCount: 0 }
@@ -108,6 +147,9 @@ class Dataset {
108
147
  const pending = this.#records.slice(this.#pushedCount)
109
148
  const records = pending.map((rec) => {
110
149
  const out = { input: rec.input }
150
+ if (rec.id != null) {
151
+ out.id = rec.id
152
+ }
111
153
  if (rec.expectedOutput !== null && rec.expectedOutput !== undefined) {
112
154
  out.expected_output = rec.expectedOutput
113
155
  }
@@ -128,20 +170,30 @@ class Dataset {
128
170
  throw new Error(`Failed to push records to dataset '${this.#name}': ${err.message}`)
129
171
  }
130
172
 
131
- // The append-records response returns created records under a top-level
132
- // `records` field, not the usual `data` envelope.
133
- const created = response?.records
173
+ // The append-records response has used both a top-level `records` array
174
+ // and JSON:API `data` resources. Accept either so generated/custom record
175
+ // ids are preserved for experiment row tagging.
176
+ const created = createdRecordsFromResponse(response)
177
+ const pushedVersion = versionFromCreatedRecords(created)
178
+ if (pushedVersion === null) {
179
+ // The dataset contents changed, but the backend did not report the new
180
+ // version. Avoid pinning later experiments to the pre-append create version.
181
+ this.#version = null
182
+ } else {
183
+ this.#version = pushedVersion
184
+ this.#latestVersion = Math.max(Number(this.#latestVersion ?? pushedVersion), pushedVersion)
185
+ }
186
+
134
187
  let pushedCount = 0
135
- if (Array.isArray(created)) {
136
- for (const node of created) {
137
- const recordId = String(node?.id ?? '')
138
- if (recordId !== '') pushedCount++
139
- this.#recordIds.push(recordId)
188
+ for (const [index, node] of created.entries()) {
189
+ const recordId = recordIdFromCreatedRecord(node)
190
+ if (recordId !== '') {
191
+ pushedCount++
192
+ pending[index].id = recordId
140
193
  }
141
- for (let i = created.length; i < pending.length; i++) this.#recordIds.push('')
142
- } else {
143
- for (let i = 0; i < pending.length; i++) this.#recordIds.push('')
194
+ this.#recordIds.push(recordId)
144
195
  }
196
+ for (let i = created.length; i < pending.length; i++) this.#recordIds.push('')
145
197
 
146
198
  // Advance by the snapshotted pending count, not the live records length,
147
199
  // so records added while this push was in flight aren't skipped by the next push.