dd-trace 5.10.0 → 5.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 (54) hide show
  1. package/index.d.ts +15 -0
  2. package/package.json +2 -1
  3. package/packages/datadog-instrumentations/src/fetch.js +6 -45
  4. package/packages/datadog-instrumentations/src/helpers/fetch.js +22 -0
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -1
  6. package/packages/datadog-instrumentations/src/jest.js +77 -10
  7. package/packages/datadog-instrumentations/src/mongoose.js +2 -1
  8. package/packages/datadog-instrumentations/src/openai.js +149 -0
  9. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +6 -1
  10. package/packages/datadog-instrumentations/src/selenium.js +69 -0
  11. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  12. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +2 -2
  13. package/packages/datadog-plugin-cypress/src/support.js +19 -3
  14. package/packages/datadog-plugin-fetch/src/index.js +20 -11
  15. package/packages/datadog-plugin-jest/src/index.js +7 -2
  16. package/packages/datadog-plugin-mocha/src/index.js +4 -5
  17. package/packages/datadog-plugin-openai/src/index.js +159 -32
  18. package/packages/datadog-plugin-openai/src/services.js +2 -1
  19. package/packages/datadog-plugin-playwright/src/index.js +2 -2
  20. package/packages/datadog-plugin-selenium/src/index.js +71 -0
  21. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +70 -0
  23. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +14 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +12 -0
  25. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-rule-type.js +6 -0
  26. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +5 -50
  27. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +742 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +539 -66
  29. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +1 -9
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -2
  31. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  32. package/packages/dd-trace/src/appsec/remote_config/index.js +5 -5
  33. package/packages/dd-trace/src/appsec/reporter.js +11 -10
  34. package/packages/dd-trace/src/appsec/telemetry.js +36 -7
  35. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  36. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  37. package/packages/dd-trace/src/config.js +94 -9
  38. package/packages/dd-trace/src/dogstatsd.js +13 -11
  39. package/packages/dd-trace/src/index.js +5 -1
  40. package/packages/dd-trace/src/noop/dogstatsd.js +11 -0
  41. package/packages/dd-trace/src/noop/proxy.js +3 -0
  42. package/packages/dd-trace/src/opentracing/propagation/text_map.js +10 -4
  43. package/packages/dd-trace/src/opentracing/span.js +2 -0
  44. package/packages/dd-trace/src/plugins/index.js +2 -0
  45. package/packages/dd-trace/src/plugins/util/test.js +34 -3
  46. package/packages/dd-trace/src/profiling/config.js +8 -4
  47. package/packages/dd-trace/src/profiling/exporters/agent.js +5 -3
  48. package/packages/dd-trace/src/profiling/profiler.js +4 -0
  49. package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +33 -0
  50. package/packages/dd-trace/src/profiling/ssi-telemetry.js +167 -0
  51. package/packages/dd-trace/src/proxy.js +33 -7
  52. package/packages/dd-trace/src/tagger.js +13 -3
  53. package/packages/dd-trace/src/telemetry/index.js +5 -4
  54. package/packages/dd-trace/src/telemetry/metrics.js +2 -2
@@ -4,28 +4,37 @@ const HttpClientPlugin = require('../../datadog-plugin-http/src/client')
4
4
 
5
5
  class FetchPlugin extends HttpClientPlugin {
6
6
  static get id () { return 'fetch' }
7
- static get prefix () { return 'apm:fetch:request' }
7
+ static get prefix () { return 'tracing:apm:fetch:request' }
8
8
 
9
- addTraceSub (eventName, handler) {
10
- this.addSub(`apm:${this.constructor.id}:${this.operation}:${eventName}`, handler)
11
- }
12
-
13
- bindStart (message) {
14
- const req = message.req
9
+ bindStart (ctx) {
10
+ const req = ctx.req
15
11
  const options = new URL(req.url)
16
12
  const headers = options.headers = Object.fromEntries(req.headers.entries())
17
13
 
18
14
  options.method = req.method
19
15
 
20
- message.args = { options }
16
+ ctx.args = { options }
21
17
 
22
- const store = super.bindStart(message)
18
+ const store = super.bindStart(ctx)
23
19
 
24
- message.headers = headers
25
- message.req = new globalThis.Request(req, { headers })
20
+ for (const name in headers) {
21
+ if (!req.headers.has(name)) {
22
+ req.headers.set(name, headers[name])
23
+ }
24
+ }
26
25
 
27
26
  return store
28
27
  }
28
+
29
+ error (ctx) {
30
+ if (ctx.error.name === 'AbortError') return
31
+ return super.error(ctx)
32
+ }
33
+
34
+ asyncEnd (ctx) {
35
+ ctx.res = ctx.result
36
+ return this.finish(ctx)
37
+ }
29
38
  }
30
39
 
31
40
  module.exports = FetchPlugin
@@ -18,7 +18,8 @@ const {
18
18
  TEST_SOURCE_FILE,
19
19
  TEST_IS_NEW,
20
20
  TEST_IS_RETRY,
21
- TEST_EARLY_FLAKE_IS_ENABLED,
21
+ TEST_EARLY_FLAKE_ENABLED,
22
+ TEST_EARLY_FLAKE_ABORT_REASON,
22
23
  JEST_DISPLAY_NAME
23
24
  } = require('../../dd-trace/src/plugins/util/test')
24
25
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -89,6 +90,7 @@ class JestPlugin extends CiPlugin {
89
90
  hasForcedToRunSuites,
90
91
  error,
91
92
  isEarlyFlakeDetectionEnabled,
93
+ isEarlyFlakeDetectionFaulty,
92
94
  onDone
93
95
  }) => {
94
96
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -115,7 +117,10 @@ class JestPlugin extends CiPlugin {
115
117
  )
116
118
 
117
119
  if (isEarlyFlakeDetectionEnabled) {
118
- this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
120
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
121
+ }
122
+ if (isEarlyFlakeDetectionFaulty) {
123
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
119
124
  }
120
125
 
121
126
  this.testModuleSpan.finish()
@@ -20,7 +20,7 @@ const {
20
20
  removeEfdStringFromTestName,
21
21
  TEST_IS_NEW,
22
22
  TEST_IS_RETRY,
23
- TEST_EARLY_FLAKE_IS_ENABLED
23
+ TEST_EARLY_FLAKE_ENABLED
24
24
  } = require('../../dd-trace/src/plugins/util/test')
25
25
  const { COMPONENT } = require('../../dd-trace/src/constants')
26
26
  const {
@@ -144,10 +144,9 @@ class MochaPlugin extends CiPlugin {
144
144
 
145
145
  this.addSub('ci:mocha:test:finish', (status) => {
146
146
  const store = storage.getStore()
147
+ const span = store?.span
147
148
 
148
- if (store && store.span) {
149
- const span = store.span
150
-
149
+ if (span) {
151
150
  span.setTag(TEST_STATUS, status)
152
151
 
153
152
  span.finish()
@@ -223,7 +222,7 @@ class MochaPlugin extends CiPlugin {
223
222
  )
224
223
 
225
224
  if (isEarlyFlakeDetectionEnabled) {
226
- this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
225
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
227
226
  }
228
227
 
229
228
  this.testModuleSpan.finish()
@@ -84,7 +84,7 @@ class OpenApiPlugin extends TracingPlugin {
84
84
  const tags = {} // The remaining tags are added one at a time
85
85
 
86
86
  // createChatCompletion, createCompletion, createImage, createImageEdit, createTranscription, createTranslation
87
- if ('prompt' in payload) {
87
+ if (payload.prompt) {
88
88
  const prompt = payload.prompt
89
89
  store.prompt = prompt
90
90
  if (typeof prompt === 'string' || (Array.isArray(prompt) && typeof prompt[0] === 'number')) {
@@ -99,7 +99,7 @@ class OpenApiPlugin extends TracingPlugin {
99
99
  }
100
100
 
101
101
  // createEdit, createEmbedding, createModeration
102
- if ('input' in payload) {
102
+ if (payload.input) {
103
103
  const normalized = normalizeStringOrTokenArray(payload.input, false)
104
104
  tags['openai.request.input'] = truncateText(normalized)
105
105
  store.input = normalized
@@ -114,41 +114,60 @@ class OpenApiPlugin extends TracingPlugin {
114
114
 
115
115
  switch (methodName) {
116
116
  case 'createFineTune':
117
+ case 'fine_tuning.jobs.create':
118
+ case 'fine-tune.create':
117
119
  createFineTuneRequestExtraction(tags, payload)
118
120
  break
119
121
 
120
122
  case 'createImage':
123
+ case 'images.generate':
121
124
  case 'createImageEdit':
125
+ case 'images.edit':
122
126
  case 'createImageVariation':
127
+ case 'images.createVariation':
123
128
  commonCreateImageRequestExtraction(tags, payload, store)
124
129
  break
125
130
 
126
131
  case 'createChatCompletion':
132
+ case 'chat.completions.create':
127
133
  createChatCompletionRequestExtraction(tags, payload, store)
128
134
  break
129
135
 
130
136
  case 'createFile':
137
+ case 'files.create':
131
138
  case 'retrieveFile':
139
+ case 'files.retrieve':
132
140
  commonFileRequestExtraction(tags, payload)
133
141
  break
134
142
 
135
143
  case 'createTranscription':
144
+ case 'audio.transcriptions.create':
136
145
  case 'createTranslation':
146
+ case 'audio.translations.create':
137
147
  commonCreateAudioRequestExtraction(tags, payload, store)
138
148
  break
139
149
 
140
150
  case 'retrieveModel':
151
+ case 'models.retrieve':
141
152
  retrieveModelRequestExtraction(tags, payload)
142
153
  break
143
154
 
144
155
  case 'listFineTuneEvents':
156
+ case 'fine_tuning.jobs.listEvents':
157
+ case 'fine-tune.listEvents':
145
158
  case 'retrieveFineTune':
159
+ case 'fine_tuning.jobs.retrieve':
160
+ case 'fine-tune.retrieve':
146
161
  case 'deleteModel':
162
+ case 'models.del':
147
163
  case 'cancelFineTune':
164
+ case 'fine_tuning.jobs.cancel':
165
+ case 'fine-tune.cancel':
148
166
  commonLookupFineTuneRequestExtraction(tags, payload)
149
167
  break
150
168
 
151
169
  case 'createEdit':
170
+ case 'edits.create':
152
171
  createEditRequestExtraction(tags, payload, store)
153
172
  break
154
173
  }
@@ -157,6 +176,10 @@ class OpenApiPlugin extends TracingPlugin {
157
176
  }
158
177
 
159
178
  finish ({ headers, body, method, path }) {
179
+ if (headers.constructor.name === 'Headers') {
180
+ headers = Object.fromEntries(headers)
181
+ }
182
+
160
183
  const span = this.activeSpan
161
184
  const methodName = span._spanContext._tags['resource.name']
162
185
 
@@ -165,11 +188,16 @@ class OpenApiPlugin extends TracingPlugin {
165
188
  const fullStore = storage.getStore()
166
189
  const store = fullStore.openai
167
190
 
191
+ if (path.startsWith('https://') || path.startsWith('http://')) {
192
+ // basic checking for if the path was set as a full URL
193
+ // not using a full regex as it will likely be "https://api.openai.com/..."
194
+ path = new URL(path).pathname
195
+ }
168
196
  const endpoint = lookupOperationEndpoint(methodName, path)
169
197
 
170
198
  const tags = {
171
199
  'openai.request.endpoint': endpoint,
172
- 'openai.request.method': method,
200
+ 'openai.request.method': method.toUpperCase(),
173
201
 
174
202
  'openai.organization.id': body.organization_id, // only available in fine-tunes endpoints
175
203
  'openai.organization.name': headers['openai-organization'],
@@ -220,7 +248,7 @@ class OpenApiPlugin extends TracingPlugin {
220
248
 
221
249
  this.metrics.distribution('openai.request.duration', duration * 1000, tags)
222
250
 
223
- if (body && ('usage' in body)) {
251
+ if (body && body.usage) {
224
252
  const promptTokens = body.usage.prompt_tokens
225
253
  const completionTokens = body.usage.completion_tokens
226
254
  this.metrics.distribution('openai.tokens.prompt', promptTokens, tags)
@@ -228,19 +256,19 @@ class OpenApiPlugin extends TracingPlugin {
228
256
  this.metrics.distribution('openai.tokens.total', promptTokens + completionTokens, tags)
229
257
  }
230
258
 
231
- if ('x-ratelimit-limit-requests' in headers) {
259
+ if (headers['x-ratelimit-limit-requests']) {
232
260
  this.metrics.gauge('openai.ratelimit.requests', Number(headers['x-ratelimit-limit-requests']), tags)
233
261
  }
234
262
 
235
- if ('x-ratelimit-remaining-requests' in headers) {
263
+ if (headers['x-ratelimit-remaining-requests']) {
236
264
  this.metrics.gauge('openai.ratelimit.remaining.requests', Number(headers['x-ratelimit-remaining-requests']), tags)
237
265
  }
238
266
 
239
- if ('x-ratelimit-limit-tokens' in headers) {
267
+ if (headers['x-ratelimit-limit-tokens']) {
240
268
  this.metrics.gauge('openai.ratelimit.tokens', Number(headers['x-ratelimit-limit-tokens']), tags)
241
269
  }
242
270
 
243
- if ('x-ratelimit-remaining-tokens' in headers) {
271
+ if (headers['x-ratelimit-remaining-tokens']) {
244
272
  this.metrics.gauge('openai.ratelimit.remaining.tokens', Number(headers['x-ratelimit-remaining-tokens']), tags)
245
273
  }
246
274
  }
@@ -275,17 +303,18 @@ function createChatCompletionRequestExtraction (tags, payload, store) {
275
303
  store.messages = payload.messages
276
304
  for (let i = 0; i < payload.messages.length; i++) {
277
305
  const message = payload.messages[i]
278
- tags[`openai.request.${i}.content`] = truncateText(message.content)
279
- tags[`openai.request.${i}.role`] = message.role
280
- tags[`openai.request.${i}.name`] = message.name
281
- tags[`openai.request.${i}.finish_reason`] = message.finish_reason
306
+ tags[`openai.request.messages.${i}.content`] = truncateText(message.content)
307
+ tags[`openai.request.messages.${i}.role`] = message.role
308
+ tags[`openai.request.messages.${i}.name`] = message.name
309
+ tags[`openai.request.messages.${i}.finish_reason`] = message.finish_reason
282
310
  }
283
311
  }
284
312
 
285
313
  function commonCreateImageRequestExtraction (tags, payload, store) {
286
314
  // createImageEdit, createImageVariation
287
- if (payload.file && typeof payload.file === 'object' && payload.file.path) {
288
- const file = path.basename(payload.file.path)
315
+ const img = payload.file || payload.image
316
+ if (img && typeof img === 'object' && img.path) {
317
+ const file = path.basename(img.path)
289
318
  tags['openai.request.image'] = file
290
319
  store.file = file
291
320
  }
@@ -305,60 +334,88 @@ function commonCreateImageRequestExtraction (tags, payload, store) {
305
334
  function responseDataExtractionByMethod (methodName, tags, body, store) {
306
335
  switch (methodName) {
307
336
  case 'createModeration':
337
+ case 'moderations.create':
308
338
  createModerationResponseExtraction(tags, body)
309
339
  break
310
340
 
311
341
  case 'createCompletion':
342
+ case 'completions.create':
312
343
  case 'createChatCompletion':
344
+ case 'chat.completions.create':
313
345
  case 'createEdit':
346
+ case 'edits.create':
314
347
  commonCreateResponseExtraction(tags, body, store)
315
348
  break
316
349
 
317
350
  case 'listFiles':
351
+ case 'files.list':
318
352
  case 'listFineTunes':
353
+ case 'fine_tuning.jobs.list':
354
+ case 'fine-tune.list':
319
355
  case 'listFineTuneEvents':
356
+ case 'fine_tuning.jobs.listEvents':
357
+ case 'fine-tune.listEvents':
320
358
  commonListCountResponseExtraction(tags, body)
321
359
  break
322
360
 
323
361
  case 'createEmbedding':
362
+ case 'embeddings.create':
324
363
  createEmbeddingResponseExtraction(tags, body)
325
364
  break
326
365
 
327
366
  case 'createFile':
367
+ case 'files.create':
328
368
  case 'retrieveFile':
369
+ case 'files.retrieve':
329
370
  createRetrieveFileResponseExtraction(tags, body)
330
371
  break
331
372
 
332
373
  case 'deleteFile':
374
+ case 'files.del':
333
375
  deleteFileResponseExtraction(tags, body)
334
376
  break
335
377
 
336
378
  case 'downloadFile':
379
+ case 'files.retrieveContent':
380
+ case 'files.content':
337
381
  downloadFileResponseExtraction(tags, body)
338
382
  break
339
383
 
340
384
  case 'createFineTune':
385
+ case 'fine_tuning.jobs.create':
386
+ case 'fine-tune.create':
341
387
  case 'retrieveFineTune':
388
+ case 'fine_tuning.jobs.retrieve':
389
+ case 'fine-tune.retrieve':
342
390
  case 'cancelFineTune':
391
+ case 'fine_tuning.jobs.cancel':
392
+ case 'fine-tune.cancel':
343
393
  commonFineTuneResponseExtraction(tags, body)
344
394
  break
345
395
 
346
396
  case 'createTranscription':
397
+ case 'audio.transcriptions.create':
347
398
  case 'createTranslation':
399
+ case 'audio.translations.create':
348
400
  createAudioResponseExtraction(tags, body)
349
401
  break
350
402
 
351
403
  case 'createImage':
404
+ case 'images.generate':
352
405
  case 'createImageEdit':
406
+ case 'images.edit':
353
407
  case 'createImageVariation':
408
+ case 'images.createVariation':
354
409
  commonImageResponseExtraction(tags, body)
355
410
  break
356
411
 
357
412
  case 'listModels':
413
+ case 'models.list':
358
414
  listModelsResponseExtraction(tags, body)
359
415
  break
360
416
 
361
417
  case 'retrieveModel':
418
+ case 'models.retrieve':
362
419
  retrieveModelResponseExtraction(tags, body)
363
420
  break
364
421
  }
@@ -431,15 +488,19 @@ function createFineTuneRequestExtraction (tags, body) {
431
488
  function commonFineTuneResponseExtraction (tags, body) {
432
489
  tags['openai.response.events_count'] = defensiveArrayLength(body.events)
433
490
  tags['openai.response.fine_tuned_model'] = body.fine_tuned_model
434
- if (body.hyperparams) {
435
- tags['openai.response.hyperparams.n_epochs'] = body.hyperparams.n_epochs
436
- tags['openai.response.hyperparams.batch_size'] = body.hyperparams.batch_size
437
- tags['openai.response.hyperparams.prompt_loss_weight'] = body.hyperparams.prompt_loss_weight
438
- tags['openai.response.hyperparams.learning_rate_multiplier'] = body.hyperparams.learning_rate_multiplier
491
+
492
+ const hyperparams = body.hyperparams || body.hyperparameters
493
+ const hyperparamsKey = body.hyperparams ? 'hyperparams' : 'hyperparameters'
494
+
495
+ if (hyperparams) {
496
+ tags[`openai.response.${hyperparamsKey}.n_epochs`] = hyperparams.n_epochs
497
+ tags[`openai.response.${hyperparamsKey}.batch_size`] = hyperparams.batch_size
498
+ tags[`openai.response.${hyperparamsKey}.prompt_loss_weight`] = hyperparams.prompt_loss_weight
499
+ tags[`openai.response.${hyperparamsKey}.learning_rate_multiplier`] = hyperparams.learning_rate_multiplier
439
500
  }
440
- tags['openai.response.training_files_count'] = defensiveArrayLength(body.training_files)
501
+ tags['openai.response.training_files_count'] = defensiveArrayLength(body.training_files || body.training_file)
441
502
  tags['openai.response.result_files_count'] = defensiveArrayLength(body.result_files)
442
- tags['openai.response.validation_files_count'] = defensiveArrayLength(body.validation_files)
503
+ tags['openai.response.validation_files_count'] = defensiveArrayLength(body.validation_files || body.validation_file)
443
504
  tags['openai.response.updated_at'] = body.updated_at
444
505
  tags['openai.response.status'] = body.status
445
506
  }
@@ -528,18 +589,31 @@ function commonCreateResponseExtraction (tags, body, store) {
528
589
 
529
590
  store.choices = body.choices
530
591
 
531
- for (let i = 0; i < body.choices.length; i++) {
532
- const choice = body.choices[i]
533
- tags[`openai.response.choices.${i}.finish_reason`] = choice.finish_reason
534
- tags[`openai.response.choices.${i}.logprobs`] = ('logprobs' in choice) ? 'returned' : undefined
535
- tags[`openai.response.choices.${i}.text`] = truncateText(choice.text)
592
+ for (let choiceIdx = 0; choiceIdx < body.choices.length; choiceIdx++) {
593
+ const choice = body.choices[choiceIdx]
594
+
595
+ // logprobs can be nullm and we still want to tag it as 'returned' even when set to 'null'
596
+ const specifiesLogProb = Object.keys(choice).indexOf('logprobs') !== -1
597
+
598
+ tags[`openai.response.choices.${choiceIdx}.finish_reason`] = choice.finish_reason
599
+ tags[`openai.response.choices.${choiceIdx}.logprobs`] = specifiesLogProb ? 'returned' : undefined
600
+ tags[`openai.response.choices.${choiceIdx}.text`] = truncateText(choice.text)
536
601
 
537
602
  // createChatCompletion only
538
- if ('message' in choice) {
603
+ if (choice.message) {
539
604
  const message = choice.message
540
- tags[`openai.response.choices.${i}.message.role`] = message.role
541
- tags[`openai.response.choices.${i}.message.content`] = truncateText(message.content)
542
- tags[`openai.response.choices.${i}.message.name`] = truncateText(message.name)
605
+ tags[`openai.response.choices.${choiceIdx}.message.role`] = message.role
606
+ tags[`openai.response.choices.${choiceIdx}.message.content`] = truncateText(message.content)
607
+ tags[`openai.response.choices.${choiceIdx}.message.name`] = truncateText(message.name)
608
+ if (message.tool_calls) {
609
+ const toolCalls = message.tool_calls
610
+ for (let toolIdx = 0; toolIdx < toolCalls.length; toolIdx++) {
611
+ tags[`openai.response.choices.${choiceIdx}.message.tool_calls.${toolIdx}.name`] =
612
+ toolCalls[toolIdx].function.name
613
+ tags[`openai.response.choices.${choiceIdx}.message.tool_calls.${toolIdx}.arguments`] =
614
+ toolCalls[toolIdx].function.arguments
615
+ }
616
+ }
543
617
  }
544
618
  }
545
619
  }
@@ -577,34 +651,62 @@ function truncateText (text) {
577
651
  function coerceResponseBody (body, methodName) {
578
652
  switch (methodName) {
579
653
  case 'downloadFile':
654
+ case 'files.retrieveContent':
655
+ case 'files.content':
580
656
  return { file: body }
581
657
  }
582
658
 
583
- return typeof body === 'object' ? body : {}
659
+ const type = typeof body
660
+ if (type === 'string') {
661
+ try {
662
+ return JSON.parse(body)
663
+ } catch {
664
+ return body
665
+ }
666
+ } else if (type === 'object') {
667
+ return body
668
+ } else {
669
+ return {}
670
+ }
584
671
  }
585
672
 
586
673
  // This method is used to replace a dynamic URL segment with an asterisk
587
674
  function lookupOperationEndpoint (operationId, url) {
588
675
  switch (operationId) {
589
676
  case 'deleteModel':
677
+ case 'models.del':
590
678
  case 'retrieveModel':
679
+ case 'models.retrieve':
591
680
  return '/v1/models/*'
592
681
 
593
682
  case 'deleteFile':
683
+ case 'files.del':
594
684
  case 'retrieveFile':
685
+ case 'files.retrieve':
595
686
  return '/v1/files/*'
596
687
 
597
688
  case 'downloadFile':
689
+ case 'files.retrieveContent':
690
+ case 'files.content':
598
691
  return '/v1/files/*/content'
599
692
 
600
693
  case 'retrieveFineTune':
694
+ case 'fine-tune.retrieve':
601
695
  return '/v1/fine-tunes/*'
696
+ case 'fine_tuning.jobs.retrieve':
697
+ return '/v1/fine_tuning/jobs/*'
602
698
 
603
699
  case 'listFineTuneEvents':
700
+ case 'fine-tune.listEvents':
604
701
  return '/v1/fine-tunes/*/events'
702
+ case 'fine_tuning.jobs.listEvents':
703
+ return '/v1/fine_tuning/jobs/*/events'
605
704
 
606
705
  case 'cancelFineTune':
706
+ case 'fine-tune.cancel':
607
707
  return '/v1/fine-tunes/*/cancel'
708
+ case 'fine_tuning.jobs.cancel':
709
+ return '/v1/fine_tuning/jobs/*/cancel'
608
710
  }
609
711
 
610
712
  return url
@@ -618,12 +720,17 @@ function lookupOperationEndpoint (operationId, url) {
618
720
  function normalizeRequestPayload (methodName, args) {
619
721
  switch (methodName) {
620
722
  case 'listModels':
723
+ case 'models.list':
621
724
  case 'listFiles':
725
+ case 'files.list':
622
726
  case 'listFineTunes':
727
+ case 'fine_tuning.jobs.list':
728
+ case 'fine-tune.list':
623
729
  // no argument
624
730
  return {}
625
731
 
626
732
  case 'retrieveModel':
733
+ case 'models.retrieve':
627
734
  return { id: args[0] }
628
735
 
629
736
  case 'createFile':
@@ -633,19 +740,30 @@ function normalizeRequestPayload (methodName, args) {
633
740
  }
634
741
 
635
742
  case 'deleteFile':
743
+ case 'files.del':
636
744
  case 'retrieveFile':
745
+ case 'files.retrieve':
637
746
  case 'downloadFile':
747
+ case 'files.retrieveContent':
748
+ case 'files.content':
638
749
  return { file_id: args[0] }
639
750
 
640
751
  case 'listFineTuneEvents':
752
+ case 'fine_tuning.jobs.listEvents':
753
+ case 'fine-tune.listEvents':
641
754
  return {
642
755
  fine_tune_id: args[0],
643
756
  stream: args[1] // undocumented
644
757
  }
645
758
 
646
759
  case 'retrieveFineTune':
760
+ case 'fine_tuning.jobs.retrieve':
761
+ case 'fine-tune.retrieve':
647
762
  case 'deleteModel':
763
+ case 'models.del':
648
764
  case 'cancelFineTune':
765
+ case 'fine_tuning.jobs.cancel':
766
+ case 'fine-tune.cancel':
649
767
  return { fine_tune_id: args[0] }
650
768
 
651
769
  case 'createImageEdit':
@@ -702,7 +820,16 @@ function normalizeStringOrTokenArray (input, truncate) {
702
820
  }
703
821
 
704
822
  function defensiveArrayLength (maybeArray) {
705
- return Array.isArray(maybeArray) ? maybeArray.length : undefined
823
+ if (maybeArray) {
824
+ if (Array.isArray(maybeArray)) {
825
+ return maybeArray.length
826
+ } else {
827
+ // case of a singular item (ie body.training_file vs body.training_files)
828
+ return 1
829
+ }
830
+ }
831
+
832
+ return undefined
706
833
  }
707
834
 
708
835
  module.exports = OpenApiPlugin
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
- const { DogStatsDClient, NoopDogStatsDClient } = require('../../dd-trace/src/dogstatsd')
3
+ const { DogStatsDClient } = require('../../dd-trace/src/dogstatsd')
4
+ const NoopDogStatsDClient = require('../../dd-trace/src/noop/dogstatsd')
4
5
  const { ExternalLogger, NoopExternalLogger } = require('../../dd-trace/src/external-logger/src')
5
6
 
6
7
  const FLUSH_INTERVAL = 10 * 1000
@@ -14,7 +14,7 @@ const {
14
14
  TEST_CONFIGURATION_BROWSER_NAME,
15
15
  TEST_IS_NEW,
16
16
  TEST_IS_RETRY,
17
- TEST_EARLY_FLAKE_IS_ENABLED
17
+ TEST_EARLY_FLAKE_ENABLED
18
18
  } = require('../../dd-trace/src/plugins/util/test')
19
19
  const { RESOURCE_NAME } = require('../../../ext/tags')
20
20
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -41,7 +41,7 @@ class PlaywrightPlugin extends CiPlugin {
41
41
  this.testSessionSpan.setTag(TEST_STATUS, status)
42
42
 
43
43
  if (isEarlyFlakeDetectionEnabled) {
44
- this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
44
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
45
45
  }
46
46
 
47
47
  if (this.numFailedSuites > 0) {
@@ -0,0 +1,71 @@
1
+ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
2
+ const { storage } = require('../../datadog-core')
3
+
4
+ const {
5
+ TEST_IS_RUM_ACTIVE,
6
+ TEST_BROWSER_DRIVER,
7
+ TEST_BROWSER_DRIVER_VERSION,
8
+ TEST_BROWSER_NAME,
9
+ TEST_BROWSER_VERSION,
10
+ TEST_TYPE
11
+ } = require('../../dd-trace/src/plugins/util/test')
12
+ const { SPAN_TYPE } = require('../../../ext/tags')
13
+
14
+ function isTestSpan (span) {
15
+ return span.context()._tags[SPAN_TYPE] === 'test'
16
+ }
17
+
18
+ function getTestSpanFromTrace (trace) {
19
+ for (const span of trace.started) {
20
+ if (isTestSpan(span)) {
21
+ return span
22
+ }
23
+ }
24
+ return null
25
+ }
26
+
27
+ class SeleniumPlugin extends CiPlugin {
28
+ static get id () {
29
+ return 'selenium'
30
+ }
31
+
32
+ constructor (...args) {
33
+ super(...args)
34
+
35
+ this.addSub('ci:selenium:driver:get', ({
36
+ setTraceId,
37
+ seleniumVersion,
38
+ browserName,
39
+ browserVersion,
40
+ isRumActive
41
+ }) => {
42
+ const store = storage.getStore()
43
+ const span = store?.span
44
+ if (!span) {
45
+ return
46
+ }
47
+ let testSpan
48
+ if (isTestSpan(span)) {
49
+ testSpan = span
50
+ } else {
51
+ testSpan = getTestSpanFromTrace(span.context()._trace)
52
+ }
53
+ if (!testSpan) {
54
+ return
55
+ }
56
+ if (setTraceId) {
57
+ setTraceId(testSpan.context().toTraceId())
58
+ }
59
+ if (isRumActive) {
60
+ testSpan.setTag(TEST_IS_RUM_ACTIVE, 'true')
61
+ }
62
+ testSpan.setTag(TEST_BROWSER_DRIVER, 'selenium')
63
+ testSpan.setTag(TEST_BROWSER_DRIVER_VERSION, seleniumVersion)
64
+ testSpan.setTag(TEST_BROWSER_NAME, browserName)
65
+ testSpan.setTag(TEST_BROWSER_VERSION, browserVersion)
66
+ testSpan.setTag(TEST_TYPE, 'browser')
67
+ })
68
+ }
69
+ }
70
+
71
+ module.exports = SeleniumPlugin
@@ -2,6 +2,7 @@
2
2
 
3
3
  module.exports = {
4
4
  COMMAND_INJECTION_ANALYZER: require('./command-injection-analyzer'),
5
+ HARCODED_PASSWORD_ANALYZER: require('./hardcoded-password-analyzer'),
5
6
  HARCODED_SECRET_ANALYZER: require('./hardcoded-secret-analyzer'),
6
7
  HEADER_INJECTION_ANALYZER: require('./header-injection-analyzer'),
7
8
  HSTS_HEADER_MISSING_ANALYZER: require('./hsts-header-missing-analyzer'),