dd-trace 6.17.0 → 6.19.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 (110) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ci/vitest-no-worker-init-setup.mjs +22 -12
  3. package/index.d.ts +55 -11
  4. package/package.json +6 -6
  5. package/packages/datadog-instrumentations/src/anthropic.js +111 -9
  6. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
  7. package/packages/datadog-instrumentations/src/cucumber.js +50 -9
  8. package/packages/datadog-instrumentations/src/cypress-config.js +27 -15
  9. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
  10. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -6
  12. package/packages/datadog-instrumentations/src/helpers/register.js +20 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +41 -4
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +15 -13
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +51 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +6 -0
  19. package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
  20. package/packages/datadog-instrumentations/src/jest.js +30 -13
  21. package/packages/datadog-instrumentations/src/mocha/main.js +7 -5
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +8 -8
  23. package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -4
  24. package/packages/datadog-instrumentations/src/playwright.js +73 -6
  25. package/packages/datadog-instrumentations/src/undici.js +7 -3
  26. package/packages/datadog-instrumentations/src/vitest-main.js +95 -21
  27. package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
  28. package/packages/datadog-instrumentations/src/webdriverio.js +28 -12
  29. package/packages/datadog-instrumentations/src/ws.js +41 -1
  30. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
  31. package/packages/datadog-plugin-cucumber/src/index.js +22 -8
  32. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -14
  33. package/packages/datadog-plugin-cypress/src/index.js +3 -1
  34. package/packages/datadog-plugin-fetch/src/index.js +3 -0
  35. package/packages/datadog-plugin-jest/src/index.js +3 -4
  36. package/packages/datadog-plugin-mocha/src/index.js +3 -4
  37. package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
  38. package/packages/datadog-plugin-playwright/src/index.js +11 -6
  39. package/packages/datadog-plugin-undici/src/index.js +5 -0
  40. package/packages/datadog-plugin-vitest/src/index.js +3 -4
  41. package/packages/datadog-plugin-ws/src/producer.js +1 -1
  42. package/packages/datadog-shimmer/src/shimmer.js +14 -10
  43. package/packages/datadog-turbopack/index.js +4 -1
  44. package/packages/datadog-turbopack/src/loader.js +6 -2
  45. package/packages/dd-trace/src/agent/info.js +5 -4
  46. package/packages/dd-trace/src/aiguard/client.js +38 -19
  47. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
  48. package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
  49. package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
  50. package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
  51. package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
  52. package/packages/dd-trace/src/config/generated-config-types.d.ts +19 -19
  53. package/packages/dd-trace/src/config/index.js +7 -6
  54. package/packages/dd-trace/src/config/supported-configurations.json +16 -5
  55. package/packages/dd-trace/src/constants.js +2 -0
  56. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
  57. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
  58. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
  59. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  60. package/packages/dd-trace/src/debugger/index.js +1 -1
  61. package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
  62. package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
  63. package/packages/dd-trace/src/evp_proxy/discovery.js +14 -10
  64. package/packages/dd-trace/src/evp_proxy/path.js +21 -7
  65. package/packages/dd-trace/src/exporters/common/url.js +33 -2
  66. package/packages/dd-trace/src/llmobs/constants/tags.js +31 -8
  67. package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
  68. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
  69. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
  70. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
  71. package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
  72. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
  73. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
  74. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
  75. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
  76. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
  77. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
  78. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
  79. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +13 -0
  80. package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
  81. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
  82. package/packages/dd-trace/src/llmobs/prompts/manager.js +10 -2
  83. package/packages/dd-trace/src/llmobs/prompts/prompt.js +79 -12
  84. package/packages/dd-trace/src/llmobs/span_processor.js +10 -65
  85. package/packages/dd-trace/src/llmobs/tagger.js +11 -40
  86. package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
  87. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
  88. package/packages/dd-trace/src/openfeature/index.js +6 -2
  89. package/packages/dd-trace/src/openfeature/writers/base.js +90 -17
  90. package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -0
  91. package/packages/dd-trace/src/openfeature/writers/util.js +104 -33
  92. package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
  93. package/packages/dd-trace/src/opentracing/tracer.js +3 -3
  94. package/packages/dd-trace/src/plugin_manager.js +27 -8
  95. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
  96. package/packages/dd-trace/src/plugins/util/test.js +13 -4
  97. package/packages/dd-trace/src/profiler.js +51 -5
  98. package/packages/dd-trace/src/profiling/profiler.js +24 -20
  99. package/packages/dd-trace/src/proxy.js +2 -2
  100. package/packages/dd-trace/src/ritm.js +12 -9
  101. package/packages/dd-trace/src/span_processor.js +6 -1
  102. package/packages/dd-trace/src/telemetry/send-data.js +2 -1
  103. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  104. package/packages/dd-trace/src/tracer.js +5 -4
  105. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  106. package/packages/datadog-instrumentations/src/azure-cosmos.js +0 -7
  107. package/packages/datadog-instrumentations/src/bullmq.js +0 -11
  108. package/packages/datadog-instrumentations/src/langchain.js +0 -7
  109. package/packages/datadog-instrumentations/src/langgraph.js +0 -7
  110. package/packages/datadog-instrumentations/src/mercurius.js +0 -11
@@ -1,5 +1,6 @@
1
1
  "component","origin","license","copyright"
2
2
  "@apm-js-collab/code-transformer","https://github.com/nodejs/orchestrion-js","['Apache-2.0']","['nodejs']"
3
+ "@bufbuild/protobuf","https://github.com/bufbuild/protobuf-es","['(Apache-2.0 AND BSD-3-Clause)']","['bufbuild']"
3
4
  "@datadog/flagging-core","https://github.com/DataDog/openfeature-js-client","['Apache-2.0']","['DataDog']"
4
5
  "@datadog/libdatadog","https://github.com/DataDog/libdatadog-nodejs","['Apache-2.0']","['DataDog']"
5
6
  "@datadog/libdatadog-extras","https://github.com/DataDog/libdatadog-nodejs","['Apache-2.0']","['Datadog Inc.']"
@@ -114,14 +114,12 @@ if (isNoWorkerInitActive) {
114
114
  onTestFinished(() => {
115
115
  recordTestAttemptTiming(task, attemptIndex, attemptStart)
116
116
  recordRetryErrorCount(task)
117
- if (
118
- (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt || isQuarantinedTest) &&
119
- attemptIndex === getFinalAttemptIndex(task)
120
- ) {
117
+ if (isEarlyFlakeDetectionTestAttempt || isQuarantinedTest ||
118
+ (isAttemptToFixTest && attemptIndex === getFinalAttemptIndex(task))) {
121
119
  if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt) {
122
- recordTestOptimizationStatus(task, attemptIndex, true)
120
+ recordTestOptimizationStatus(task, attemptIndex, attemptIndex === getFinalAttemptIndex(task))
123
121
  }
124
- switchQuarantinedFinalFailure(task, attemptIndex)
122
+ switchQuarantinedFinalFailure(task)
125
123
  }
126
124
  finishRumCorrelation(task, attemptIndex)
127
125
  })
@@ -134,7 +132,7 @@ if (isNoWorkerInitActive) {
134
132
  recordTestOptimizationStatus(task, attemptIndex)
135
133
  }
136
134
  if (!restoredEarlyFlakeDetectionResult) {
137
- switchQuarantinedFinalFailure(task, attemptIndex)
135
+ switchQuarantinedFinalFailure(task)
138
136
  }
139
137
  })
140
138
  }
@@ -433,7 +431,10 @@ function recordEarlyFlakeDetectionStatus (task, attemptIndex, onlyIfNewErrors) {
433
431
  return
434
432
  }
435
433
 
436
- if (!earlyFlakeDetectionRetriesByTask.has(task)) {
434
+ // onTestFinished runs before aroundEach fixture teardown. Select the budget
435
+ // when the next attempt records the completed attempt instead.
436
+ const isCompletedAttempt = attemptIndex < task.meta.__ddTestOptCurrentAttemptIndex
437
+ if (isCompletedAttempt && !earlyFlakeDetectionRetriesByTask.has(task)) {
437
438
  const retryCount = getEarlyFlakeDetectionRetryCount(task)
438
439
  earlyFlakeDetectionRetriesByTask.set(task, retryCount)
439
440
  task.repeats = retryCount
@@ -452,9 +453,15 @@ function recordEarlyFlakeDetectionStatus (task, attemptIndex, onlyIfNewErrors) {
452
453
  )
453
454
  task.meta.__ddTestOptEfdErrorCounts[attemptIndex] = task.result?.errors?.length || 0
454
455
 
455
- if (attemptIndex === getEarlyFlakeDetectionRetryCountForTask(task) &&
456
+ // Vitest 5.0.3 retains failures across repeats. Defer EFD's overall failure until
457
+ // the last attempt, while keeping each attempt's actual outcome in the metadata.
458
+ if (attemptIndex < getEarlyFlakeDetectionRetryCountForTask(task) ||
456
459
  task.meta.__ddTestOptEfdStatuses.includes('pass')) {
457
460
  task.result.state = 'pass'
461
+ } else if (isCompletedAttempt) {
462
+ // A zero retry budget cancels repeats after we suppressed the initial failure.
463
+ task.result.state = 'fail'
464
+ markQuarantinedFailure(task)
458
465
  }
459
466
  }
460
467
 
@@ -525,7 +532,7 @@ function wrapRetryCondition (task) {
525
532
  shouldRetry = condition(error)
526
533
  }
527
534
 
528
- if (!shouldRetry && (task.result?.repeatCount || 0) >= (task.repeats || 0)) {
535
+ if (!shouldRetry) {
529
536
  const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
530
537
  recordTestOptimizationStatus(task, attemptIndex)
531
538
  markQuarantinedFailure(task)
@@ -579,7 +586,7 @@ function getFinalAttemptIndex (task) {
579
586
  return attemptIndex + retriesRemaining + (repeatsRemaining * (retryLimit + 1))
580
587
  }
581
588
 
582
- function switchQuarantinedFinalFailure (task, attemptIndex) {
589
+ function switchQuarantinedFinalFailure (task) {
583
590
  const testSuite = getTestSuite(task)
584
591
  const testName = getTestName(task)
585
592
  if (
@@ -590,7 +597,10 @@ function switchQuarantinedFinalFailure (task, attemptIndex) {
590
597
  return
591
598
  }
592
599
 
593
- if (attemptIndex < getFinalAttemptIndex(task)) {
600
+ // Finish the retries in this repetition before suppressing its failure. A later
601
+ // repetition cannot undo a failure that Vitest has already aggregated.
602
+ const retryAttemptIndex = retryAttemptIndexByTask.get(task)?.index || 0
603
+ if (retryAttemptIndex < getRetryLimit(task)) {
594
604
  return
595
605
  }
596
606
 
package/index.d.ts CHANGED
@@ -2025,8 +2025,9 @@ declare namespace tracer {
2025
2025
  /**
2026
2026
  * Whether to capture LLM Observability spans for this integration. When set to `false`,
2027
2027
  * the integration keeps emitting APM spans and propagating trace context, but no LLM
2028
- * Observability spans are produced. Useful when another integration already captures the
2029
- * same operation and the payloads would otherwise be stored twice.
2028
+ * Observability spans and no basic `gen_ai.*` APM tags are produced. Useful when another
2029
+ * integration already captures the same operation and the payloads would otherwise be
2030
+ * stored twice.
2030
2031
  * @default true
2031
2032
  */
2032
2033
  llmobs?: boolean;
@@ -3886,13 +3887,13 @@ declare namespace tracer {
3886
3887
  /** Create a text or chat prompt and its first version. */
3887
3888
  createPrompt (
3888
3889
  promptId: string,
3889
- template: string | PromptTemplateMessage[],
3890
+ template: string | PromptTemplateItem[],
3890
3891
  options?: CreatePromptOptions
3891
3892
  ): Promise<PromptResponse>
3892
3893
  /** Add a text or chat version to an existing prompt. */
3893
3894
  createPromptVersion (
3894
3895
  promptId: string,
3895
- template: string | PromptTemplateMessage[],
3896
+ template: string | PromptTemplateItem[],
3896
3897
  options?: CreatePromptVersionOptions
3897
3898
  ): Promise<PromptVersionResponse>
3898
3899
  /** Update prompt metadata. */
@@ -3916,10 +3917,45 @@ declare namespace tracer {
3916
3917
  content: string
3917
3918
  }
3918
3919
 
3920
+ /** Provider fields on expanded messages; authored templates remain text-only. */
3921
+ interface FormattedPromptMessage extends PromptTemplateMessage {
3922
+ // The inherited content type stays string for compatibility; tool-only payloads can omit it or contain null.
3923
+ tool_calls?: PromptToolCall[] | null,
3924
+ tool_results?: PromptToolResult[] | null,
3925
+ tool_call_id?: string | null
3926
+ }
3927
+
3928
+ interface PromptToolCall {
3929
+ id?: string | null,
3930
+ type?: string | null,
3931
+ tool_id?: string | null,
3932
+ name?: string | null,
3933
+ arguments?: unknown,
3934
+ function?: {
3935
+ name: string,
3936
+ arguments: string
3937
+ } | null
3938
+ }
3939
+
3940
+ interface PromptToolResult {
3941
+ id?: string | null,
3942
+ type?: string | null,
3943
+ tool_id?: string | null,
3944
+ name?: string | null,
3945
+ result?: unknown
3946
+ }
3947
+
3948
+ interface PromptMessagePlaceholder {
3949
+ type: 'placeholder',
3950
+ name: string
3951
+ }
3952
+
3953
+ type PromptTemplateItem = PromptTemplateMessage | PromptMessagePlaceholder
3954
+
3919
3955
  type PromptFallbackValue =
3920
3956
  | string
3921
- | PromptTemplateMessage[]
3922
- | { template: string | PromptTemplateMessage[], version?: string }
3957
+ | PromptTemplateItem[]
3958
+ | { template: string | PromptTemplateItem[], version?: string, config?: Record<string, JSONType> }
3923
3959
  type PromptFallback = PromptFallbackValue | (() => PromptFallbackValue)
3924
3960
 
3925
3961
  interface GetPromptOptions {
@@ -3938,13 +3974,15 @@ declare namespace tracer {
3938
3974
  title?: string,
3939
3975
  description?: string,
3940
3976
  userVersion?: string,
3941
- envIds?: string[]
3977
+ envIds?: string[],
3978
+ config?: Record<string, JSONType>
3942
3979
  }
3943
3980
 
3944
3981
  interface CreatePromptVersionOptions {
3945
3982
  description?: string,
3946
3983
  userVersion?: string,
3947
- envIds?: string[]
3984
+ envIds?: string[],
3985
+ config?: Record<string, JSONType>
3948
3986
  }
3949
3987
 
3950
3988
  interface UpdatePromptOptions {
@@ -3957,14 +3995,16 @@ declare namespace tracer {
3957
3995
  envIds?: string[]
3958
3996
  }
3959
3997
 
3998
+ // Preserve released output types; placeholder entries and null/omitted tool content are not fully described here.
3960
3999
  interface ManagedPrompt {
3961
4000
  readonly id: string,
3962
4001
  readonly version: string,
3963
4002
  readonly source: 'registry' | 'cache' | 'fallback' | 'ff' | 'resolve',
3964
4003
  readonly template: string | ReadonlyArray<Readonly<PromptTemplateMessage>>,
4004
+ readonly config: Readonly<Record<string, ReadonlyJSONType>>,
3965
4005
  readonly promptUuid?: string,
3966
4006
  readonly promptVersionUuid?: string,
3967
- format (variables?: Record<string, unknown>): string | PromptTemplateMessage[]
4007
+ format (variables?: Record<string, unknown>): string | FormattedPromptMessage[]
3968
4008
  toAnnotation (variables?: Record<string, unknown>): Prompt
3969
4009
  }
3970
4010
 
@@ -3982,7 +4022,8 @@ declare namespace tracer {
3982
4022
  ml_app?: string,
3983
4023
  ml_apps?: string[],
3984
4024
  last_version_created_at?: string,
3985
- extracted_from?: string
4025
+ extracted_from?: string,
4026
+ config?: Record<string, JSONType>
3986
4027
  }
3987
4028
 
3988
4029
  interface PromptVersionResponse {
@@ -3996,7 +4037,8 @@ declare namespace tracer {
3996
4037
  version_created_at?: string,
3997
4038
  author?: string,
3998
4039
  description?: string,
3999
- ml_app?: string
4040
+ ml_app?: string,
4041
+ config?: Record<string, JSONType>
4000
4042
  }
4001
4043
 
4002
4044
  interface DeletedPromptResponse {
@@ -4008,6 +4050,8 @@ declare namespace tracer {
4008
4050
  /** JSON-serializable value accepted by LLMObs Experiments. */
4009
4051
  type JSONType = string | number | boolean | null | JSONType[] | { [key: string]: JSONType }
4010
4052
 
4053
+ type ReadonlyJSONType = string | number | boolean | null | ReadonlyArray<ReadonlyJSONType> | { readonly [key: string]: ReadonlyJSONType }
4054
+
4011
4055
  /**
4012
4056
  * A task run over each dataset record during an experiment.
4013
4057
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.17.0",
3
+ "version": "6.19.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -206,7 +206,7 @@
206
206
  "@eslint/eslintrc": "^3.3.7",
207
207
  "@eslint/js": "^10.0.1",
208
208
  "@eslint/plugin-kit": "^0.7.3",
209
- "@datadog/openfeature-node-server": "2.3.0",
209
+ "@datadog/openfeature-node-server": "2.4.0",
210
210
  "@msgpack/msgpack": "^3.1.3",
211
211
  "@openfeature/core": "^1.12.0",
212
212
  "@openfeature/server-sdk": "~1.23.0",
@@ -222,14 +222,14 @@
222
222
  "bun": "1.4.2",
223
223
  "c8": "^12.0.0",
224
224
  "codeowners-audit": "^2.9.0",
225
- "eslint": "^10.10.0",
225
+ "eslint": "^10.11.0",
226
226
  "eslint-plugin-cypress": "^7.0.2",
227
227
  "eslint-plugin-import-x": "^4.16.2",
228
- "eslint-plugin-jsdoc": "^64.5.2",
228
+ "eslint-plugin-jsdoc": "^64.5.4",
229
229
  "eslint-plugin-mocha": "^12.0.2",
230
230
  "eslint-plugin-n": "^18.3.0",
231
231
  "eslint-plugin-promise": "^7.3.0",
232
- "eslint-plugin-regexp": "^3.3.0",
232
+ "eslint-plugin-regexp": "^3.3.1",
233
233
  "eslint-plugin-sonarjs": "^4.2.1",
234
234
  "eslint-plugin-unicorn": "^74.0.0",
235
235
  "express": "^5.1.0",
@@ -251,7 +251,7 @@
251
251
  "node-preload": "^0.2.1",
252
252
  "nyc": "^18.0.0",
253
253
  "octokit": "^5.0.3",
254
- "p-limit": "^7.3.2",
254
+ "p-limit": "^7.3.3",
255
255
  "proxyquire": "^2.1.3",
256
256
  "retry": "^0.13.1",
257
257
  "semifies": "^1.0.0",
@@ -124,13 +124,48 @@ function wrapStreamIterator (iterator, ctx) {
124
124
  }
125
125
  }
126
126
 
127
+ /**
128
+ * Gives the SDK a detached copy of the response to decode, leaving the body the caller can still
129
+ * read through `asResponse()` untouched. The bytes are buffered instead of streamed from a live
130
+ * clone, and the controller is replaced, because the SDK cancels its reader and aborts its
131
+ * controller whenever a stream ends early: that abort would kill the caller's request, and on a
132
+ * cloned body that cancel blocks until the other branch is read, which the caller cannot do while
133
+ * it is still waiting for this parse.
134
+ *
135
+ * Any failure falls back to the original props and tells the wrapper to skip inspection, leaving
136
+ * the SDK to behave as it does untraced.
137
+ *
138
+ * @param {{response: Response}} props
139
+ * @param {() => void} onFailure
140
+ * @returns {object|Promise<object>}
141
+ */
142
+ function detachResponse (props, onFailure) {
143
+ const { status, statusText, headers } = props.response
144
+ try {
145
+ return props.response.clone().arrayBuffer()
146
+ .then(body => ({
147
+ ...props,
148
+ response: new Response(body, { status, statusText, headers }),
149
+ controller: new AbortController(),
150
+ }))
151
+ .catch(() => {
152
+ onFailure()
153
+ return props
154
+ })
155
+ } catch {
156
+ onFailure()
157
+ return props
158
+ }
159
+ }
160
+
127
161
  function wrapCreate (create) {
128
162
  return function (...args) {
129
163
  const stream = args[0]?.stream
130
- const preparing = !stream && messagesPrepareChannel.hasSubscribers
131
- const intercepting = !stream && messagesInterceptChannel.hasSubscribers
164
+ const tracing = anthropicTracingChannel.start.hasSubscribers
165
+ const preparing = messagesPrepareChannel.hasSubscribers
166
+ const intercepting = messagesInterceptChannel.hasSubscribers
132
167
 
133
- if (!anthropicTracingChannel.start.hasSubscribers && !preparing && !intercepting) {
168
+ if (!tracing && !preparing && !intercepting) {
134
169
  return create.apply(this, args)
135
170
  }
136
171
 
@@ -160,23 +195,60 @@ function wrapCreate (create) {
160
195
 
161
196
  let parseResult
162
197
  let wrappedResponse
198
+ // Set around a `parse()` whose body a raw-response reader also holds, so the SDK decodes a
199
+ // copy and leaves that body to the caller. Plain streaming has no second reader and copies
200
+ // nothing.
201
+ let detach = false
163
202
 
203
+ // These helpers live on each returned APIPromise, so there is no module export for
204
+ // Orchestrion to replace; wrap this instance instead.
164
205
  shimmer.wrap(apiPromise, 'parse', parse => function (...parseArgs) {
165
206
  if (parseResult) return parseResult
166
207
 
167
- parseResult = heldUntil(parse.apply(this, parseArgs), interceptCtx?.beforeResult?.())
208
+ let detachmentFailed = false
209
+ const responsePromise = detach ? this.responsePromise : undefined
210
+ if (responsePromise) {
211
+ this.responsePromise = responsePromise.then(props => detachResponse(props, () => {
212
+ detachmentFailed = true
213
+ }))
214
+ }
215
+
216
+ let result
217
+ try {
218
+ result = parse.apply(this, parseArgs)
219
+ } finally {
220
+ if (responsePromise) this.responsePromise = responsePromise
221
+ }
222
+
223
+ const onResult = interceptCtx?.onResult
224
+ parseResult = heldUntil(result, interceptCtx?.beforeResult?.())
168
225
  .then(response => {
169
226
  if (stream) {
170
- shimmer.wrap(response, Symbol.asyncIterator, iterator => wrapStreamIterator(iterator, ctx))
171
- return response
227
+ if (!onResult || detachmentFailed) {
228
+ if (tracing) {
229
+ shimmer.wrap(response, Symbol.asyncIterator, iterator => wrapStreamIterator(iterator, ctx))
230
+ }
231
+ return response
232
+ }
233
+
234
+ return Promise.resolve(onResult(response)).then(deliveredResponse => {
235
+ if (tracing) {
236
+ shimmer.wrap(
237
+ deliveredResponse,
238
+ Symbol.asyncIterator,
239
+ iterator => wrapStreamIterator(iterator, ctx)
240
+ )
241
+ }
242
+ return deliveredResponse
243
+ })
172
244
  }
173
245
 
174
- if (!interceptCtx?.onResult) {
246
+ if (!onResult) {
175
247
  finish(ctx, response)
176
248
  return response
177
249
  }
178
250
 
179
- return Promise.resolve(interceptCtx.onResult(response)).then(deliveredResponse => {
251
+ return Promise.resolve(onResult(response)).then(deliveredResponse => {
180
252
  finish(ctx, response)
181
253
  return deliveredResponse
182
254
  })
@@ -190,9 +262,26 @@ function wrapCreate (create) {
190
262
  shimmer.wrap(apiPromise, 'asResponse', origAsResponse => function (...asResponseArgs) {
191
263
  return heldUntil(origAsResponse.apply(this, asResponseArgs), interceptCtx?.beforeResult?.())
192
264
  .then(response => {
265
+ if (stream) {
266
+ // A caller holding the parsed stream closes the span as it drains it. When only
267
+ // the raw body is read, decode the stream here so it is still evaluated.
268
+ if (!interceptCtx?.onResult) return response
269
+ if (parseResult) return parseResult.then(() => response)
270
+
271
+ detach = true
272
+ try {
273
+ return apiPromise.parse().then(() => {
274
+ finish(ctx)
275
+ return response
276
+ })
277
+ } finally {
278
+ detach = false
279
+ }
280
+ }
281
+
193
282
  // Wrap json()/text()/clone() so the span still closes on the raw-response path,
194
283
  // and not twice for the same response.
195
- if (!stream && wrappedResponse !== response) {
284
+ if (wrappedResponse !== response) {
196
285
  wrappedResponse = response
197
286
  wrapRawResponse(response, ctx, interceptCtx)
198
287
  }
@@ -202,6 +291,19 @@ function wrapCreate (create) {
202
291
  })
203
292
  }
204
293
 
294
+ // `withResponse()` is the one SDK call that hands out the raw response *and* the parsed
295
+ // stream; it invokes `parse()` synchronously, so the flag only has to outlive that call.
296
+ if (stream && interceptCtx?.onResult && typeof apiPromise.withResponse === 'function') {
297
+ shimmer.wrap(apiPromise, 'withResponse', withResponse => function (...withResponseArgs) {
298
+ detach = true
299
+ try {
300
+ return withResponse.apply(this, withResponseArgs)
301
+ } finally {
302
+ detach = false
303
+ }
304
+ })
305
+ }
306
+
205
307
  anthropicTracingChannel.end.publish(ctx)
206
308
 
207
309
  return apiPromise
@@ -44,6 +44,7 @@ const chunkEmitTimes = new WeakMap()
44
44
  * tools: Map<string, ToolState>,
45
45
  * subagents: Map<string, Record<string, unknown>>,
46
46
  * prompt?: string,
47
+ * systemPrompt?: string | string[] | { type: string, prompt?: string | string[], append?: string },
47
48
  * model?: string,
48
49
  * resume?: string,
49
50
  * maxTurns?: number,
@@ -235,6 +236,7 @@ function onQueryStart (ctx) {
235
236
  const prompt = queryArg.prompt
236
237
  const sessionCtx = {
237
238
  prompt: typeof prompt === 'string' ? prompt : undefined,
239
+ systemPrompt: options.systemPrompt,
238
240
  model: options.model,
239
241
  resume: options.resume,
240
242
  maxTurns: options.maxTurns,
@@ -451,6 +453,7 @@ function processStep (
451
453
  llmCh.traceSync(() => {}, {
452
454
  model,
453
455
  usage,
456
+ systemPrompt: parentToolUseId ? undefined : sessionCtx?.systemPrompt,
454
457
  startTime: stepStartTime,
455
458
  finishTime: chunkEmitTimes.get(chunks[messageEndIdx - 1]),
456
459
  chunks,
@@ -529,7 +532,8 @@ function processChunks (chunks, agentCtx) {
529
532
  const sessionCtx = agentCtx.sessionCtx
530
533
  const getLifecycle = createStreamLookup(chunks)
531
534
 
532
- const { type, subtype, ...rest } = chunks[0]
535
+ const initChunk = chunks.find(chunk => chunk.type === 'system' && chunk.subtype === 'init') || chunks[0]
536
+ const { type, subtype, ...rest } = initChunk
533
537
  Object.assign(agentCtx, rest)
534
538
  if (sessionCtx) {
535
539
  if (sessionCtx.sessionId) agentCtx.session_id = sessionCtx.sessionId
@@ -142,7 +142,6 @@ let knownTests = {}
142
142
  let skippedSuites = []
143
143
  let isSuitesSkipped = false
144
144
  let areAllSuitesSkipped = false
145
- let hasTestsToRun = false
146
145
  let repositoryRoot
147
146
 
148
147
  function shouldRunEarlyFlakeDetection () {
@@ -1218,7 +1217,6 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1218
1217
  }
1219
1218
  }
1220
1219
 
1221
- hasTestsToRun = isCoordinator ? this.sourcedPickles.length > 0 : this.pickleIds.length > 0
1222
1220
  pickleByFile = isCoordinator ? getPickleByFileNew(this) : getPickleByFile(this)
1223
1221
 
1224
1222
  if (isKnownTestsEnabled) {
@@ -1330,10 +1328,17 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1330
1328
  global.__coverage__ = fromCoverageMapToCoverage(originalCoverageMap)
1331
1329
  }
1332
1330
 
1333
- const isExpectedEmptySession = success && !hasTestsToRun
1331
+ const testCaseAttempts = (this.eventDataCollector || eventDataCollector).getTestCaseAttempts()
1332
+ const getStatus = satisfies(frameworkVersion, '>=7.3.0') ? getStatusFromResultLatest : getStatusFromResult
1333
+ const hasExecutedTests = testCaseAttempts.some(({ worstTestStepResult }) =>
1334
+ getStatus(worstTestStepResult).status !== 'skip'
1335
+ )
1336
+ const testSessionEmptyReason = success && !hasExecutedTests
1337
+ ? (testCaseAttempts.length > 0 || skippedSuites.length > 0 ? 'all_tests_skipped' : 'zero_tests')
1338
+ : undefined
1334
1339
  const flushPromise = getChannelPromise(sessionFinishCh, {
1335
- status: isExpectedEmptySession ? 'skip' : (success ? 'pass' : 'fail'),
1336
- isExpectedEmptySession,
1340
+ status: testSessionEmptyReason ? 'skip' : (success ? 'pass' : 'fail'),
1341
+ testSessionEmptyReason,
1337
1342
  isSuitesSkipped,
1338
1343
  testCodeCoverageLinesTotal,
1339
1344
  testSessionCoverageFiles,
@@ -1413,13 +1418,49 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1413
1418
 
1414
1419
  if (isImpactedTestsEnabled) {
1415
1420
  const setIsModified = (receivedIsModified) => { isModified = receivedIsModified }
1416
- const scenarios = gherkinDocument.feature?.children?.filter(
1417
- children => pickle.astNodeIds.includes(children.scenario.id)
1418
- ).map(scenario => scenario.scenario)
1421
+ const gherkinNodes = []
1422
+ // Prefix ranges catch removed Backgrounds, which have no AST node in the target document.
1423
+ const gherkinScopeRanges = []
1424
+ const feature = gherkinDocument.feature
1425
+ const featureChildren = feature?.children
1426
+ if (featureChildren?.length) {
1427
+ const firstFeatureChild = featureChildren[0]
1428
+ const firstFeatureNode = firstFeatureChild.background ?? firstFeatureChild.rule ?? firstFeatureChild.scenario
1429
+ const firstFeatureLine = firstFeatureNode.tags?.[0]?.location.line ?? firstFeatureNode.location.line
1430
+ const featurePrefix = [feature.location.line, firstFeatureLine - 1]
1431
+ let featureBackground
1432
+ for (const child of featureChildren) {
1433
+ if (child.background) {
1434
+ featureBackground = child.background
1435
+ continue
1436
+ }
1437
+ const rule = child.rule
1438
+ const children = rule?.children ?? [child]
1439
+ let ruleBackground
1440
+ for (const { background } of children) {
1441
+ if (background) ruleBackground = background
1442
+ }
1443
+ for (const { scenario } of children) {
1444
+ if (scenario && pickle.astNodeIds.includes(scenario.id)) {
1445
+ if (!featureBackground) gherkinScopeRanges.push(featurePrefix)
1446
+ if (rule?.children?.length && !ruleBackground) {
1447
+ const firstRuleChild = rule.children.at(0)
1448
+ const firstRuleNode = firstRuleChild.background ?? firstRuleChild.scenario
1449
+ const firstRuleLine = firstRuleNode.tags?.[0]?.location.line ?? firstRuleNode.location.line
1450
+ gherkinScopeRanges.push([rule.location.line, firstRuleLine - 1])
1451
+ }
1452
+ if (featureBackground) gherkinNodes.push(featureBackground)
1453
+ if (ruleBackground) gherkinNodes.push(ruleBackground)
1454
+ gherkinNodes.push(scenario)
1455
+ }
1456
+ }
1457
+ }
1458
+ }
1419
1459
  const stepIds = testCase?.testSteps?.flatMap(testStep => testStep.stepDefinitionIds)
1420
1460
 
1421
1461
  isModifiedCh.publish({
1422
- scenarios,
1462
+ gherkinNodes,
1463
+ gherkinScopeRanges,
1423
1464
  testFileAbsolutePath: gherkinDocument.uri,
1424
1465
  modifiedFiles,
1425
1466
  stepIds,