@apifuse/provider-sdk 2.2.0-beta.62 → 2.2.0-beta.64

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 (261) hide show
  1. package/AUTHORING.md +367 -9
  2. package/CHANGELOG.md +8 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1 -0
  5. package/bin/apifuse-check.ts +84 -2
  6. package/bin/apifuse-dev.ts +11 -31
  7. package/bin/apifuse-migrate-deployment.ts +200 -0
  8. package/bin/apifuse-pack-check.ts +11 -4
  9. package/bin/apifuse-pack-types.ts +187 -4
  10. package/bin/apifuse-record.ts +44 -32
  11. package/bin/apifuse-submit-check.ts +9 -5
  12. package/bin/apifuse-test.ts +3 -1
  13. package/dist/cli/commands.d.ts +1 -1
  14. package/dist/cli/commands.js +11 -0
  15. package/dist/cli/deployment-intent.d.ts +178 -0
  16. package/dist/cli/deployment-intent.js +277 -0
  17. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  18. package/dist/cli/migrate-deployment-intent.js +1552 -0
  19. package/dist/cli/migrate-provider-shape.d.ts +5 -1
  20. package/dist/cli/migrate-provider-shape.js +4 -2
  21. package/dist/cli/resolver-runtime.d.ts +21 -0
  22. package/dist/cli/resolver-runtime.js +31 -0
  23. package/dist/define.d.ts +3 -0
  24. package/dist/define.js +5 -0
  25. package/dist/dev.d.ts +3 -1
  26. package/dist/dev.js +6 -1
  27. package/dist/engine.d.ts +35 -1
  28. package/dist/engine.js +21 -2
  29. package/dist/error-resolution.d.ts +1 -0
  30. package/dist/error-resolution.js +54 -0
  31. package/dist/errors.d.ts +39 -1
  32. package/dist/errors.js +50 -0
  33. package/dist/handle-meta.d.ts +36 -1
  34. package/dist/handle-meta.js +16 -0
  35. package/dist/handle.d.ts +23 -5
  36. package/dist/handle.js +54 -8
  37. package/dist/health-scenario.d.ts +38 -0
  38. package/dist/i18n/error-messages.d.ts +86 -0
  39. package/dist/i18n/error-messages.js +192 -0
  40. package/dist/i18n/index.d.ts +1 -0
  41. package/dist/i18n/index.js +1 -0
  42. package/dist/i18n/keys.d.ts +12 -0
  43. package/dist/i18n/keys.js +76 -5
  44. package/dist/index.d.ts +11 -3
  45. package/dist/index.js +9 -1
  46. package/dist/lint.d.ts +15 -0
  47. package/dist/lint.js +469 -82
  48. package/dist/provider.d.ts +6 -6
  49. package/dist/provider.js +3 -3
  50. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  51. package/dist/runtime/browser-telemetry-binding.js +83 -0
  52. package/dist/runtime/browser-telemetry.d.ts +76 -0
  53. package/dist/runtime/browser-telemetry.js +165 -0
  54. package/dist/runtime/browser.d.ts +3 -0
  55. package/dist/runtime/browser.js +208 -47
  56. package/dist/runtime/cache-telemetry.d.ts +71 -0
  57. package/dist/runtime/cache-telemetry.js +177 -0
  58. package/dist/runtime/cache.js +67 -7
  59. package/dist/runtime/capability-response.d.ts +3 -0
  60. package/dist/runtime/capability-response.js +56 -0
  61. package/dist/runtime/capability-telemetry.d.ts +46 -0
  62. package/dist/runtime/capability-telemetry.js +68 -0
  63. package/dist/runtime/diagnostic-env.d.ts +3 -0
  64. package/dist/runtime/diagnostic-env.js +10 -1
  65. package/dist/runtime/engine-mode.d.ts +74 -0
  66. package/dist/runtime/engine-mode.js +152 -0
  67. package/dist/runtime/handle.js +9 -1
  68. package/dist/runtime/http-telemetry-guard.d.ts +10 -0
  69. package/dist/runtime/http-telemetry-guard.js +62 -10
  70. package/dist/runtime/http.js +141 -35
  71. package/dist/runtime/instrumentation.js +92 -13
  72. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  73. package/dist/runtime/ocr-telemetry.js +192 -0
  74. package/dist/runtime/ocr.js +35 -8
  75. package/dist/runtime/request-telemetry.d.ts +2 -0
  76. package/dist/runtime/request-telemetry.js +2 -1
  77. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  78. package/dist/runtime/resolver-runtime-options.js +33 -0
  79. package/dist/runtime/resolver-shared.d.ts +3 -2
  80. package/dist/runtime/resolver-shared.js +4 -4
  81. package/dist/runtime/resolver-telemetry.d.ts +1 -0
  82. package/dist/runtime/resolver-telemetry.js +1 -1
  83. package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
  84. package/dist/runtime/resolver-vendors/browser.js +46 -0
  85. package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
  86. package/dist/runtime/resolver-vendors/hosts.js +35 -0
  87. package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
  88. package/dist/runtime/resolver-vendors/types.d.ts +5 -4
  89. package/dist/runtime/resolver.d.ts +4 -1
  90. package/dist/runtime/resolver.js +46 -13
  91. package/dist/runtime/state-telemetry.d.ts +79 -0
  92. package/dist/runtime/state-telemetry.js +194 -0
  93. package/dist/runtime/state.d.ts +3 -0
  94. package/dist/runtime/state.js +26 -7
  95. package/dist/runtime/stealth-cookies.d.ts +5 -1
  96. package/dist/runtime/stealth-cookies.js +21 -1
  97. package/dist/runtime/stealth-owned-headers.d.ts +26 -0
  98. package/dist/runtime/stealth-owned-headers.js +37 -0
  99. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  100. package/dist/runtime/stealth-telemetry.js +179 -0
  101. package/dist/runtime/stealth.d.ts +4 -1
  102. package/dist/runtime/stealth.js +228 -85
  103. package/dist/runtime/stt-telemetry.d.ts +77 -0
  104. package/dist/runtime/stt-telemetry.js +176 -0
  105. package/dist/runtime/stt.js +15 -6
  106. package/dist/runtime-boundary-lint.d.ts +132 -0
  107. package/dist/runtime-boundary-lint.js +586 -0
  108. package/dist/server/error-stack-frames.d.ts +35 -0
  109. package/dist/server/error-stack-frames.js +219 -0
  110. package/dist/server/index.d.ts +7 -0
  111. package/dist/server/index.js +4 -0
  112. package/dist/server/sdk-version.d.ts +3 -0
  113. package/dist/server/sdk-version.js +19 -0
  114. package/dist/server/self-test.js +1 -18
  115. package/dist/server/sensitive-values.d.ts +1 -1
  116. package/dist/server/sensitive-values.js +9 -0
  117. package/dist/server/serve-implementation.d.ts +51 -2
  118. package/dist/server/serve-implementation.js +337 -90
  119. package/dist/server/types.d.ts +1 -0
  120. package/dist/server/types.js +7 -0
  121. package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
  122. package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
  123. package/dist/stateful/stateful-provider-runtime-context.js +1 -0
  124. package/dist/types.d.ts +123 -1
  125. package/dist/typescript-module.d.ts +39 -0
  126. package/dist/typescript-module.js +72 -0
  127. package/package.json +6 -1
  128. package/src/cli/commands.ts +14 -0
  129. package/src/cli/deployment-intent.ts +364 -0
  130. package/src/cli/migrate-deployment-intent.ts +1908 -0
  131. package/src/cli/migrate-provider-shape.ts +10 -4
  132. package/src/cli/resolver-runtime.ts +72 -0
  133. package/src/define.ts +13 -0
  134. package/src/dev.ts +9 -2
  135. package/src/engine.ts +38 -2
  136. package/src/error-resolution.ts +58 -0
  137. package/src/errors.ts +77 -1
  138. package/src/handle-meta.ts +51 -1
  139. package/src/handle.ts +92 -10
  140. package/src/health-scenario.ts +38 -0
  141. package/src/i18n/error-messages.ts +255 -0
  142. package/src/i18n/index.ts +1 -0
  143. package/src/i18n/keys.ts +80 -6
  144. package/src/index.ts +86 -0
  145. package/src/lint.ts +604 -91
  146. package/src/provider.ts +12 -0
  147. package/src/runtime/browser-telemetry-binding.ts +92 -0
  148. package/src/runtime/browser-telemetry.ts +289 -0
  149. package/src/runtime/browser.ts +249 -65
  150. package/src/runtime/cache-telemetry.ts +247 -0
  151. package/src/runtime/cache.ts +68 -17
  152. package/src/runtime/capability-response.ts +53 -0
  153. package/src/runtime/capability-telemetry.ts +101 -0
  154. package/src/runtime/diagnostic-env.ts +11 -1
  155. package/src/runtime/engine-mode.ts +233 -0
  156. package/src/runtime/handle.ts +9 -1
  157. package/src/runtime/http-telemetry-guard.ts +66 -13
  158. package/src/runtime/http.ts +168 -37
  159. package/src/runtime/instrumentation.ts +105 -16
  160. package/src/runtime/ocr-telemetry.ts +308 -0
  161. package/src/runtime/ocr.ts +114 -74
  162. package/src/runtime/request-telemetry.ts +2 -1
  163. package/src/runtime/resolver-runtime-options.ts +50 -0
  164. package/src/runtime/resolver-shared.ts +9 -4
  165. package/src/runtime/resolver-telemetry.ts +1 -1
  166. package/src/runtime/resolver-vendors/browser.ts +65 -7
  167. package/src/runtime/resolver-vendors/hosts.ts +43 -0
  168. package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
  169. package/src/runtime/resolver-vendors/types.ts +5 -4
  170. package/src/runtime/resolver.ts +64 -21
  171. package/src/runtime/state-telemetry.ts +298 -0
  172. package/src/runtime/state.ts +46 -45
  173. package/src/runtime/stealth-cookies.ts +24 -1
  174. package/src/runtime/stealth-owned-headers.ts +41 -0
  175. package/src/runtime/stealth-telemetry.ts +304 -0
  176. package/src/runtime/stealth.ts +355 -150
  177. package/src/runtime/stt-telemetry.ts +282 -0
  178. package/src/runtime/stt.ts +93 -71
  179. package/src/runtime-boundary-lint.ts +703 -0
  180. package/src/server/error-stack-frames.ts +214 -0
  181. package/src/server/index.ts +53 -0
  182. package/src/server/sdk-version.ts +22 -0
  183. package/src/server/self-test.ts +1 -21
  184. package/src/server/sensitive-values.ts +14 -0
  185. package/src/server/serve-implementation.ts +569 -108
  186. package/src/server/types.ts +7 -0
  187. package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
  188. package/src/stateful/stateful-provider-runtime-context.ts +4 -0
  189. package/src/types.ts +128 -1
  190. package/src/typescript-module.ts +100 -0
  191. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
  192. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
  193. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
  194. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
  195. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
  196. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
  197. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
  198. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
  199. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
  200. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
  201. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
  202. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
  203. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
  204. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
  205. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
  206. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
  207. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
  208. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
  209. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
  210. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
  211. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
  212. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
  213. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
  214. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
  215. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
  216. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
  217. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
  218. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
  219. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
  220. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
  221. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
  222. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
  223. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
  224. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
  225. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
  226. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
  227. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
  244. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
  245. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
  246. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
  247. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
  248. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
  249. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
  250. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
  251. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
  252. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
  253. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
  254. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
  255. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
  256. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
  257. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
  258. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
  259. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
  260. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
  261. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
@@ -0,0 +1,179 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { closedEnum } from "./request-telemetry.js";
3
+ const MAX_ATTEMPT_SAMPLES = 24;
4
+ const MAX_DIAGNOSTIC_TEXT = 300;
5
+ function boundedInteger(value) {
6
+ if (!Number.isFinite(value))
7
+ return Number.MAX_SAFE_INTEGER;
8
+ return Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, Math.floor(value)));
9
+ }
10
+ function saturatingAdd(a, b) {
11
+ if (!Number.isFinite(a) || !Number.isFinite(b))
12
+ return Number.MAX_SAFE_INTEGER;
13
+ return Math.min(Number.MAX_SAFE_INTEGER, boundedInteger(a) + boundedInteger(b));
14
+ }
15
+ function boundedText(value, redact) {
16
+ let result = value;
17
+ try {
18
+ result = redact?.(value) ?? value;
19
+ }
20
+ catch {
21
+ result = "[REDACTION_FAILED]";
22
+ }
23
+ const length = Math.min(result.length, MAX_DIAGNOSTIC_TEXT);
24
+ const units = Buffer.alloc(length * 2);
25
+ for (let index = 0; index < length; index += 1) {
26
+ units.writeUInt16LE(result.charCodeAt(index), index * 2);
27
+ }
28
+ return units.toString("utf16le");
29
+ }
30
+ function cloneDiagnostics(diagnostics, redact) {
31
+ if (!diagnostics)
32
+ return undefined;
33
+ const result = {
34
+ ...(diagnostics.name === undefined ? {} : { name: boundedText(diagnostics.name, redact) }),
35
+ ...(diagnostics.message === undefined
36
+ ? {}
37
+ : { message: boundedText(diagnostics.message, redact) }),
38
+ ...(diagnostics.code === undefined ? {} : { code: boundedText(diagnostics.code, redact) }),
39
+ };
40
+ return Object.keys(result).length > 0 ? result : undefined;
41
+ }
42
+ function cloneProfile(profile) {
43
+ return { browser: profile.browser, os: profile.os };
44
+ }
45
+ export class StealthTelemetryCollector {
46
+ key = "stealth";
47
+ #redact;
48
+ #attempts = 0;
49
+ #poolRefreshes = 0;
50
+ #redirectHops = 0;
51
+ #profileId;
52
+ #proxyUsed = false;
53
+ #requestClass;
54
+ #sbsdDetected;
55
+ #sbsdOutcome;
56
+ #safeRefetch = 0;
57
+ #lastStatus;
58
+ #ms = 0;
59
+ #attemptSamples = [];
60
+ #attemptSamplesDropped = 0;
61
+ constructor(options = {}) {
62
+ this.#redact = options.redact;
63
+ }
64
+ recordAttempt(event) {
65
+ const ms = boundedInteger(event.ms);
66
+ this.#attempts = saturatingAdd(this.#attempts, 1);
67
+ this.#ms = saturatingAdd(this.#ms, ms);
68
+ this.#profileId = cloneProfile(event.profileId);
69
+ this.#proxyUsed ||= event.proxyUsed;
70
+ this.#requestClass = event.requestClass;
71
+ if (event.status !== undefined)
72
+ this.#lastStatus = boundedInteger(event.status);
73
+ if (this.#attemptSamples.length >= MAX_ATTEMPT_SAMPLES) {
74
+ this.#attemptSamplesDropped = saturatingAdd(this.#attemptSamplesDropped, 1);
75
+ return;
76
+ }
77
+ const sample = {
78
+ n: this.#attempts,
79
+ ms,
80
+ ...(event.status === undefined ? {} : { status: boundedInteger(event.status) }),
81
+ ...(event.errorCode === undefined ? {} : { e: event.errorCode }),
82
+ ...(event.kind === undefined ? {} : { kind: event.kind }),
83
+ ...(event.diagnostics
84
+ ? { diagnostics: cloneDiagnostics(event.diagnostics, this.#redact) }
85
+ : {}),
86
+ };
87
+ if (sample.diagnostics === undefined)
88
+ delete sample.diagnostics;
89
+ this.#attemptSamples.push(sample);
90
+ }
91
+ recordPoolRefresh() {
92
+ this.#poolRefreshes = saturatingAdd(this.#poolRefreshes, 1);
93
+ }
94
+ recordRedirectHop() {
95
+ this.#redirectHops = saturatingAdd(this.#redirectHops, 1);
96
+ }
97
+ recordSbsd(event) {
98
+ if (typeof event === "string") {
99
+ this.#sbsdDetected = true;
100
+ this.#sbsdOutcome = event;
101
+ return;
102
+ }
103
+ if (!event)
104
+ return;
105
+ this.#sbsdDetected = (this.#sbsdDetected ?? false) || event.detected;
106
+ if (event.outcome !== undefined)
107
+ this.#sbsdOutcome = event.outcome;
108
+ }
109
+ recordSafeRefetch() {
110
+ this.#safeRefetch = saturatingAdd(this.#safeRefetch, 1);
111
+ }
112
+ toLogPayload() {
113
+ if (this.#attempts === 0 &&
114
+ this.#poolRefreshes === 0 &&
115
+ this.#redirectHops === 0 &&
116
+ this.#safeRefetch === 0 &&
117
+ this.#sbsdDetected === undefined) {
118
+ return undefined;
119
+ }
120
+ return {
121
+ attempts: this.#attempts,
122
+ poolRefreshes: this.#poolRefreshes,
123
+ redirectHops: this.#redirectHops,
124
+ ...(this.#profileId ? { profileId: cloneProfile(this.#profileId) } : {}),
125
+ proxyUsed: this.#proxyUsed,
126
+ ...(this.#requestClass ? { requestClass: this.#requestClass } : {}),
127
+ ...(this.#sbsdDetected === undefined ? {} : { sbsdDetected: this.#sbsdDetected }),
128
+ ...(this.#sbsdOutcome ? { sbsdOutcome: this.#sbsdOutcome } : {}),
129
+ safeRefetch: this.#safeRefetch,
130
+ ...(this.#lastStatus === undefined ? {} : { lastStatus: this.#lastStatus }),
131
+ ms: this.#ms,
132
+ ...(this.#attemptSamples.length > 0
133
+ ? {
134
+ attemptSamples: this.#attemptSamples.map((sample) => ({
135
+ ...sample,
136
+ ...(sample.diagnostics
137
+ ? {
138
+ diagnostics: { ...sample.diagnostics },
139
+ }
140
+ : {}),
141
+ })),
142
+ }
143
+ : {}),
144
+ ...(this.#attemptSamplesDropped > 0
145
+ ? { attemptSamplesDropped: this.#attemptSamplesDropped }
146
+ : {}),
147
+ };
148
+ }
149
+ toHeaderPayload(log) {
150
+ const attemptSamples = log.attemptSamples?.map((sample) => ({
151
+ n: sample.n,
152
+ ms: sample.ms,
153
+ ...(sample.status === undefined ? {} : { status: sample.status }),
154
+ ...(sample.e ? { e: closedEnum(sample.e) } : {}),
155
+ ...(sample.kind ? { kind: closedEnum(sample.kind) } : {}),
156
+ }));
157
+ return {
158
+ attempts: log.attempts,
159
+ poolRefreshes: log.poolRefreshes,
160
+ redirectHops: log.redirectHops,
161
+ ...(log.profileId
162
+ ? {
163
+ profileId: {
164
+ browser: closedEnum(log.profileId.browser),
165
+ os: closedEnum(log.profileId.os),
166
+ },
167
+ }
168
+ : {}),
169
+ proxyUsed: log.proxyUsed,
170
+ ...(log.requestClass ? { requestClass: closedEnum(log.requestClass) } : {}),
171
+ ...(log.sbsdDetected === undefined ? {} : { sbsdDetected: log.sbsdDetected }),
172
+ ...(log.sbsdOutcome ? { sbsdOutcome: closedEnum(log.sbsdOutcome) } : {}),
173
+ safeRefetch: log.safeRefetch,
174
+ ...(log.lastStatus === undefined ? {} : { lastStatus: log.lastStatus }),
175
+ ms: log.ms,
176
+ ...(attemptSamples && attemptSamples.length > 0 ? { attemptSamples } : {}),
177
+ };
178
+ }
179
+ }
@@ -1,8 +1,12 @@
1
1
  import type { BrowserProfile, EmulationOS } from "wreq-js";
2
2
  import type { ProxyResolutionOptions } from "../config/loader.js";
3
3
  import type { StealthClient, StealthProfileDescriptor, StealthProfileSelection, StealthResponse } from "../types.js";
4
+ export type { StealthTelemetryAttemptEvent, StealthTelemetryAttemptKind, StealthTelemetryDiagnostics, StealthTelemetryErrorCode, StealthTelemetryRequestClass, StealthTelemetrySbsdEvent, StealthTelemetrySbsdOutcome, StealthTelemetrySink, } from "./stealth-telemetry.js";
5
+ import type { StealthTelemetrySink } from "./stealth-telemetry.js";
4
6
  export declare const DEFAULT_STEALTH_PROFILE: StealthProfileDescriptor;
5
7
  export type StealthClientOptions = ProxyResolutionOptions & {
8
+ /** Request-scoped stealth transport observations. */
9
+ stealthTelemetry?: StealthTelemetrySink;
6
10
  warn?: (message: string) => void;
7
11
  /** Abort all requests issued by this client. */
8
12
  signal?: AbortSignal;
@@ -54,4 +58,3 @@ export declare function resolveWreqProfile(selection: StealthProfileSelection, w
54
58
  };
55
59
  export declare function normalizeResponse(response: StealthTransportResponse, requestUrl?: string, maxBodyBytes?: number): Promise<StealthResponse>;
56
60
  export declare function createStealthClient(baseUrl: string, clientOptions?: StealthClientOptions): StealthClient;
57
- export {};
@@ -4,6 +4,8 @@ import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, Prox
4
4
  import { SDKError, TransportError } from "../errors.js";
5
5
  import { DEFAULT_STEALTH_BROWSER, DEFAULT_STEALTH_OS, getStealthProfile, resolverClientProfileFamily, resolveStealthProfileSelection, } from "../stealth/profiles.js";
6
6
  import { chrome149HeaderOrder } from "./chrome149-header-order.js";
7
+ import { registerDiagnosticValue } from "./diagnostic-env.js";
8
+ import { isStealthOwnedHeaderName } from "./stealth-owned-headers.js";
7
9
  import { ENGINE_CEREMONY_EGRESS_LEASE, } from "./egress-lease.js";
8
10
  import { createProxyAuthIpDeniedError, createProxyEdgeAuthRejectedError, createProxyEdgeTlsRejectedError, createProxyPoolExhaustedError, createProxyPoolStaleError, isProxyAuthIpDeniedMessage, isProxyEdgeAuthRejectedMessage, isProxyEdgeTlsRejectedResponse, isProxyPoolRefreshableError, isProxyPoolStaleMessage, isProxyPoolStaleStatus, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_STALE_CODE, } from "./proxy-errors.js";
9
11
  import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, isProxyTransportRetryMethod, normalizeProxyTransportRetryOptions, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy.js";
@@ -171,15 +173,6 @@ function normalizedHeaderEntries(headers) {
171
173
  }
172
174
  return entries;
173
175
  }
174
- const SDK_OWNED_EXACT_CHROME_HEADERS = new Set([
175
- "host",
176
- "connection",
177
- "user-agent",
178
- "sec-ch-ua",
179
- "sec-ch-ua-mobile",
180
- "sec-ch-ua-platform",
181
- "accept-encoding",
182
- ]);
183
176
  // Non-pseudo-header order of real Chrome 149. The fixture captures
184
177
  // (chrome-ground-truth-capture.json, chrome-extended-capture.json,
185
178
  // h1-casing-capture.json) were taken through Playwright's `locale` option, which
@@ -344,7 +337,7 @@ function normalizedCallerHeaderEntriesFromRecord(headers) {
344
337
  }
345
338
  function assertCallerHeadersSupported(entries) {
346
339
  for (const [name] of entries) {
347
- if (SDK_OWNED_EXACT_CHROME_HEADERS.has(name) || name.startsWith("sec-fetch-")) {
340
+ if (isStealthOwnedHeaderName(name)) {
348
341
  throw new SDKError(`Stealth transport owns the "${name}" header; remove it from headers.`, {
349
342
  code: "STEALTH_HEADER_OVERRIDE_UNSUPPORTED",
350
343
  });
@@ -895,7 +888,99 @@ function discardStealthRedirectBody(response) {
895
888
  // failure must not replace or delay that decision.
896
889
  }
897
890
  }
898
- async function fetchStealthRedirectChain(transport, cookieJar, requestUrl, method, options, signal, buildHeaders) {
891
+ /** Telemetry must never replace a transport result, including with caller-provided sinks. */
892
+ function emitStealthTelemetry(sink, record) {
893
+ if (!sink)
894
+ return;
895
+ try {
896
+ record(sink);
897
+ }
898
+ catch {
899
+ /* The request owns its result. */
900
+ }
901
+ }
902
+ function stripStealthCookieMaterial(text, cookieNames) {
903
+ // Header-shaped Cookie/Set-Cookie material is redacted wholesale, including
904
+ // foreign names. Bare pairs outside a header are limited to jar-known names:
905
+ // a foreign bare name is indistinguishable from benign key=value prose and is
906
+ // therefore covered only when layer 1 registered its value.
907
+ let result = text.replace(/(^|[^\p{L}\p{N}_-])[\t ]*(?:cookie|set-cookie)[\t ]*(?::|=)[^\r\n]*/giu, "$1[REDACTED]");
908
+ result = result.replace(/"(?:cookie|set-cookie)"[\t ]*:[\t ]*"(?:\\.|[^"\\])*"/giu, "[REDACTED]");
909
+ for (const name of cookieNames) {
910
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
911
+ result = result.replace(new RegExp(`(^|[;,&?\\s])${escaped}\\s*=\\s*[^;,\\s&\\r\\n]+`, "giu"), "$1[REDACTED]");
912
+ }
913
+ return result;
914
+ }
915
+ function startStealthExchange(telemetry, method, options, signal) {
916
+ if (!telemetry)
917
+ return () => { };
918
+ const startedAt = Date.now();
919
+ const requestClass = chromeRequestClass(method, options.stealth?.requestClass);
920
+ let recorded = false;
921
+ return (status, error) => {
922
+ if (recorded)
923
+ return;
924
+ recorded = true;
925
+ emitStealthTelemetry(telemetry?.sink, (sink) => {
926
+ let code;
927
+ if (error !== undefined) {
928
+ try {
929
+ code = signal?.aborted
930
+ ? "transport_cancelled"
931
+ : normalizeStealthTransportError(error).code;
932
+ }
933
+ catch {
934
+ code = "other";
935
+ }
936
+ }
937
+ const knownCodes = [
938
+ "transport_network_error",
939
+ "transport_timeout",
940
+ "transport_cancelled",
941
+ "upstream_http_error",
942
+ "response_too_large",
943
+ "proxy_connect_failed",
944
+ "PROXY_POOL_STALE",
945
+ "PROXY_EDGE_AUTH_REJECTED",
946
+ "PROXY_AUTH_IP_DENIED",
947
+ "PROXY_EDGE_TLS_REJECTED",
948
+ "PROXY_REQUIRED",
949
+ ];
950
+ const errorCode = code
951
+ ? knownCodes.includes(code)
952
+ ? code
953
+ : "other"
954
+ : status !== undefined && status >= 400
955
+ ? "upstream_http_error"
956
+ : undefined;
957
+ const strip = (text) => stripStealthCookieMaterial(text, telemetry.cookieJar?.names() ?? []);
958
+ sink.recordAttempt({
959
+ ms: Date.now() - startedAt,
960
+ profileId: telemetry.profileId,
961
+ proxyUsed: telemetry.proxyUsed,
962
+ kind: telemetry.kind,
963
+ requestClass: requestClass === "navigation" && options.stealth?.userActivation === false
964
+ ? "script_navigation"
965
+ : requestClass,
966
+ ...(status === undefined ? {} : { status }),
967
+ ...(errorCode ? { errorCode } : {}),
968
+ ...(error === undefined
969
+ ? {}
970
+ : {
971
+ diagnostics: {
972
+ name: strip(error instanceof Error ? error.name : "Error"),
973
+ message: strip(error instanceof Error ? error.message : String(error)),
974
+ ...(error instanceof Error && "code" in error && typeof error.code === "string"
975
+ ? { code: strip(error.code) }
976
+ : {}),
977
+ },
978
+ }),
979
+ });
980
+ });
981
+ };
982
+ }
983
+ async function fetchStealthRedirectChain(transport, cookieJar, requestUrl, method, options, signal, buildHeaders, telemetry) {
899
984
  let currentUrl = requestUrl;
900
985
  let currentMethod = method;
901
986
  let currentBody = options.body === undefined ? undefined : normalizeBody(options.body);
@@ -903,80 +988,98 @@ async function fetchStealthRedirectChain(transport, cookieJar, requestUrl, metho
903
988
  let followedHops = 0;
904
989
  let response;
905
990
  const deadline = options.timeout ? performance.now() + options.timeout : undefined;
906
- while (true) {
907
- throwIfAmbientAborted(signal);
908
- const headers = { ...currentHeaders };
909
- if (!hasHeader(headers, "cookie")) {
910
- const cookieHeader = cookieJar.toHeader(currentUrl);
911
- if (cookieHeader)
912
- headers.cookie = cookieHeader;
913
- }
914
- const requestInit = {
915
- headers: buildHeaders
916
- ? buildHeaders(currentUrl, currentMethod, currentBody, headers)
917
- : headers,
918
- method: currentMethod,
919
- redirect: "manual",
920
- ...(new URL(currentUrl).protocol === "http:" ? { disableDefaultHeaders: true } : {}),
921
- ...(signal ? { signal } : {}),
922
- };
923
- if (currentBody !== undefined)
924
- requestInit.body = currentBody;
925
- await transport.clearCookies();
926
- throwIfAmbientAborted(signal);
927
- const remainingTimeout = deadline === undefined ? undefined : Math.ceil(deadline - performance.now());
928
- if (remainingTimeout !== undefined && remainingTimeout <= 0) {
929
- throw new TransportError("Request timed out", {
930
- code: "transport_timeout",
931
- status: 0,
932
- });
933
- }
934
- if (remainingTimeout !== undefined)
935
- requestInit.timeout = remainingTimeout;
936
- response = await transport.fetch(currentUrl, requestInit);
937
- if (signal?.aborted) {
938
- discardStealthRedirectBody(response);
939
- throw toAmbientCancellationError(signal);
940
- }
941
- cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers), response.url ?? currentUrl);
942
- if (!isRedirectStatus(response.status) || options.redirect === "manual")
943
- break;
944
- if (options.redirect === "error") {
945
- discardStealthRedirectBody(response);
946
- throw new TransportError("Stealth request encountered a redirect", {
947
- code: "transport_network_error",
948
- status: 0,
949
- });
950
- }
951
- const nextUrl = resolveRedirectUrl(response.headers.get("location") ?? undefined, response.url ?? currentUrl);
952
- if (!nextUrl)
953
- break;
954
- if (followedHops >= MAX_STEALTH_REDIRECT_HOPS) {
991
+ let finishExchange;
992
+ let status;
993
+ let exchangeError;
994
+ try {
995
+ while (true) {
996
+ throwIfAmbientAborted(signal);
997
+ const headers = { ...currentHeaders };
998
+ if (!hasHeader(headers, "cookie")) {
999
+ const cookieHeader = cookieJar.toHeader(currentUrl);
1000
+ if (cookieHeader)
1001
+ headers.cookie = cookieHeader;
1002
+ }
1003
+ const requestInit = {
1004
+ headers: buildHeaders
1005
+ ? buildHeaders(currentUrl, currentMethod, currentBody, headers)
1006
+ : headers,
1007
+ method: currentMethod,
1008
+ redirect: "manual",
1009
+ ...(new URL(currentUrl).protocol === "http:" ? { disableDefaultHeaders: true } : {}),
1010
+ ...(signal ? { signal } : {}),
1011
+ };
1012
+ if (currentBody !== undefined)
1013
+ requestInit.body = currentBody;
1014
+ await transport.clearCookies();
1015
+ throwIfAmbientAborted(signal);
1016
+ const remainingTimeout = deadline === undefined ? undefined : Math.ceil(deadline - performance.now());
1017
+ if (remainingTimeout !== undefined && remainingTimeout <= 0) {
1018
+ throw new TransportError("Request timed out", {
1019
+ code: "transport_timeout",
1020
+ status: 0,
1021
+ });
1022
+ }
1023
+ if (remainingTimeout !== undefined)
1024
+ requestInit.timeout = remainingTimeout;
1025
+ status = undefined;
1026
+ finishExchange = startStealthExchange(telemetry, currentMethod, options, signal);
1027
+ response = await transport.fetch(currentUrl, requestInit);
1028
+ status = response.status;
1029
+ if (signal?.aborted) {
1030
+ discardStealthRedirectBody(response);
1031
+ throw toAmbientCancellationError(signal);
1032
+ }
1033
+ cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers), response.url ?? currentUrl);
1034
+ if (!isRedirectStatus(response.status) || options.redirect === "manual")
1035
+ break;
1036
+ if (options.redirect === "error") {
1037
+ discardStealthRedirectBody(response);
1038
+ throw new TransportError("Stealth request encountered a redirect", {
1039
+ code: "transport_network_error",
1040
+ status: 0,
1041
+ });
1042
+ }
1043
+ const nextUrl = resolveRedirectUrl(response.headers.get("location") ?? undefined, response.url ?? currentUrl);
1044
+ if (!nextUrl)
1045
+ break;
1046
+ if (followedHops >= MAX_STEALTH_REDIRECT_HOPS) {
1047
+ discardStealthRedirectBody(response);
1048
+ throw new TransportError(`Stealth request exceeded the ${MAX_STEALTH_REDIRECT_HOPS}-redirect limit`, { code: "transport_network_error", status: 0 });
1049
+ }
1050
+ assertStealthRedirectUrl(nextUrl);
955
1051
  discardStealthRedirectBody(response);
956
- throw new TransportError(`Stealth request exceeded the ${MAX_STEALTH_REDIRECT_HOPS}-redirect limit`, { code: "transport_network_error", status: 0 });
957
- }
958
- assertStealthRedirectUrl(nextUrl);
959
- discardStealthRedirectBody(response);
960
- const nextMethod = nextRedirectMethod(response.status, currentMethod);
961
- if (nextMethod !== currentMethod) {
962
- currentBody = undefined;
963
- currentHeaders = withoutRedirectBodyHeaders(currentHeaders);
1052
+ const nextMethod = nextRedirectMethod(response.status, currentMethod);
1053
+ if (nextMethod !== currentMethod) {
1054
+ currentBody = undefined;
1055
+ currentHeaders = withoutRedirectBodyHeaders(currentHeaders);
1056
+ }
1057
+ currentMethod = nextMethod;
1058
+ currentUrl = nextUrl;
1059
+ followedHops += 1;
1060
+ emitStealthTelemetry(telemetry?.sink, (sink) => sink.recordRedirectHop());
1061
+ finishExchange(status);
1062
+ finishExchange = undefined;
964
1063
  }
965
- currentMethod = nextMethod;
966
- currentUrl = nextUrl;
967
- followedHops += 1;
1064
+ const normalized = await normalizeResponseWithSignal(response, currentUrl, options.maxBodyBytes, signal);
1065
+ if (followedHops > 0)
1066
+ normalized.redirected = true;
1067
+ return { normalized, response };
1068
+ }
1069
+ catch (error) {
1070
+ exchangeError = error;
1071
+ throw error;
1072
+ }
1073
+ finally {
1074
+ finishExchange?.(status, exchangeError);
968
1075
  }
969
- const normalized = await normalizeResponseWithSignal(response, currentUrl, options.maxBodyBytes, signal);
970
- if (followedHops > 0)
971
- normalized.redirected = true;
972
- return { normalized, response };
973
1076
  }
974
1077
  function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
975
1078
  const clients = new Map();
976
1079
  let closed = false;
977
1080
  let hasWarnedMissingProxy = false;
978
1081
  const warn = clientOptions.warn ?? console.warn;
979
- const cookieJar = new StealthCookieJar([], baseUrl);
1082
+ const cookieJar = new StealthCookieJar([], baseUrl, registerDiagnosticValue);
980
1083
  const akamaiSbsdState = { transactions: new Map() };
981
1084
  const challengedReplays = new WeakMap();
982
1085
  const ceremonyEgressLease = clientOptions[ENGINE_CEREMONY_EGRESS_LEASE];
@@ -1316,6 +1419,8 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
1316
1419
  // StealthFetchOptions), any other non-2xx honours it, and the proxy attempt is
1317
1420
  // recorded once per delivered response.
1318
1421
  const finalizeAttempt = (attemptResponse, attemptNormalized) => {
1422
+ if (attemptNormalized.challenge)
1423
+ emitStealthTelemetry(clientOptions.stealthTelemetry, (sink) => sink.recordSbsd(attemptNormalized.challenge.outcome));
1319
1424
  if (!attemptNormalized.challenge &&
1320
1425
  attemptResponse.status >= 400 &&
1321
1426
  options.throwOnHttpError !== false) {
@@ -1406,7 +1511,15 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
1406
1511
  initialHeaders.cookie = cookieHeader;
1407
1512
  }
1408
1513
  const defaultHeaders = buildOrderedHeaders?.(requestUrl, method, requestBody, initialHeaders);
1409
- const fetchOnBoundSession = (fetchUrl, fetchMethod, fetchOptions, fetchSignal, orderedHeaders = buildOrderedHeaders, sessionDefaultHeaders = defaultHeaders) => withClient(requestProfile, proxy, ignoreTlsErrors, (transport) => fetchStealthRedirectChain(transport, cookieJar, fetchUrl, fetchMethod, fetchOptions, fetchSignal, orderedHeaders), fetchSignal, sessionDefaultHeaders);
1514
+ const fetchOnBoundSession = (fetchUrl, fetchMethod, fetchOptions, fetchSignal, orderedHeaders = buildOrderedHeaders, sessionDefaultHeaders = defaultHeaders, kind = "request") => withClient(requestProfile, proxy, ignoreTlsErrors, (transport) => fetchStealthRedirectChain(transport, cookieJar, fetchUrl, fetchMethod, fetchOptions, fetchSignal, orderedHeaders, clientOptions.stealthTelemetry
1515
+ ? {
1516
+ sink: clientOptions.stealthTelemetry,
1517
+ profileId: requestProfile,
1518
+ proxyUsed: Boolean(proxy),
1519
+ kind,
1520
+ cookieJar,
1521
+ }
1522
+ : undefined), fetchSignal, sessionDefaultHeaders);
1410
1523
  const throwProxyTransportFault = (faultResponse, faultBody) => {
1411
1524
  if (!proxy)
1412
1525
  return;
@@ -1440,7 +1553,10 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
1440
1553
  const detected = akamaiSbsd
1441
1554
  ? detectAkamaiSbsdChallenge(normalized, requestUrl, cookieJar, akamaiSbsd.allowedHosts, akamaiSbsdState)
1442
1555
  : undefined;
1556
+ if (akamaiSbsd && !detected)
1557
+ emitStealthTelemetry(clientOptions.stealthTelemetry, (sink) => sink.recordSbsd({ detected: false }));
1443
1558
  if (detected && akamaiSbsd) {
1559
+ emitStealthTelemetry(clientOptions.stealthTelemetry, (sink) => sink.recordSbsd("detected"));
1444
1560
  if (challengeSolveAttempted || challengeRefetchAttempted) {
1445
1561
  normalized.challenge = {
1446
1562
  challenge: detected,
@@ -1506,7 +1622,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
1506
1622
  ...(init.maxBodyBytes === undefined
1507
1623
  ? {}
1508
1624
  : { maxBodyBytes: init.maxBodyBytes }),
1509
- }, boundSignal, resolverBuildHeaders, resolverBuildHeaders?.(transportUrl, init.method, init.body, normalizeHeaders(transportHeaders)));
1625
+ }, boundSignal, resolverBuildHeaders, resolverBuildHeaders?.(transportUrl, init.method, init.body, normalizeHeaders(transportHeaders)), "resolver");
1510
1626
  return {
1511
1627
  status: result.normalized.status,
1512
1628
  headers: result.normalized.headers,
@@ -1562,6 +1678,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
1562
1678
  }
1563
1679
  const transactionResult = await transaction.result;
1564
1680
  if (!transactionResult.solved) {
1681
+ emitStealthTelemetry(clientOptions.stealthTelemetry, (sink) => sink.recordSbsd("solve_failed"));
1565
1682
  if (ownsTransaction) {
1566
1683
  // The proxy delivered the challenged response; the resolver failed.
1567
1684
  // Surface that failure as-is: it is not a transport fault to normalize
@@ -1596,9 +1713,12 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
1596
1713
  attemptRecorded = false;
1597
1714
  attemptStartedAt = Date.now();
1598
1715
  }
1716
+ if (!explicitReplay)
1717
+ emitStealthTelemetry(clientOptions.stealthTelemetry, (sink) => sink.recordSafeRefetch());
1599
1718
  const replayed = await fetchOnBoundSession(requestUrl, method, replayOptions, clientOptions.signal);
1600
1719
  throwProxyTransportFault(replayed.response, replayed.normalized.body);
1601
1720
  const persisted = detectAkamaiSbsdChallenge(replayed.normalized, requestUrl, cookieJar, akamaiSbsd.allowedHosts, akamaiSbsdState);
1721
+ emitStealthTelemetry(clientOptions.stealthTelemetry, (sink) => sink.recordSbsd(persisted ? "challenge_persisted" : "refetch_clear"));
1602
1722
  if (persisted) {
1603
1723
  replayed.normalized.challenge = {
1604
1724
  challenge: persisted,
@@ -1718,6 +1838,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
1718
1838
  stalePoolError &&
1719
1839
  refreshAttempt < MAX_POLICY_PROXY_POOL_REFRESHES) {
1720
1840
  throwIfAmbientAborted(clientOptions.signal);
1841
+ emitStealthTelemetry(clientOptions.stealthTelemetry, (sink) => sink.recordPoolRefresh());
1721
1842
  await invalidateProxyResolutionCacheAsync({
1722
1843
  proxyPolicy: clientOptions.proxyPolicy,
1723
1844
  upstream: clientOptions.upstream,
@@ -1924,6 +2045,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
1924
2045
  if (decision.nextMethod !== method) {
1925
2046
  body = undefined;
1926
2047
  }
2048
+ emitStealthTelemetry(clientOptions.stealthTelemetry, (sink) => sink.recordRedirectHop());
1927
2049
  method = decision.nextMethod;
1928
2050
  currentUrl = decision.nextUrl;
1929
2051
  }
@@ -1967,13 +2089,34 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
1967
2089
  throwIfAmbientAborted(clientOptions.signal);
1968
2090
  await client.clearCookies();
1969
2091
  throwIfAmbientAborted(clientOptions.signal);
1970
- const response = await client.fetch(PROXY_AUTH_DIAGNOSTIC_URL, {
1971
- method: "GET",
1972
- timeout: PROXY_AUTH_DIAGNOSTIC_TIMEOUT_MS,
1973
- ...(clientOptions.signal ? { signal: clientOptions.signal } : {}),
1974
- });
1975
- const normalized = await normalizeResponseWithSignal(response, undefined, undefined, clientOptions.signal);
1976
- return classifyProxyAuthDiagnosticMessage(normalized.body);
2092
+ const finishExchange = startStealthExchange(clientOptions.stealthTelemetry
2093
+ ? {
2094
+ sink: clientOptions.stealthTelemetry,
2095
+ profileId: profile,
2096
+ proxyUsed: true,
2097
+ kind: "proxy_diagnostic",
2098
+ cookieJar,
2099
+ }
2100
+ : undefined, "GET", {}, clientOptions.signal);
2101
+ let status;
2102
+ let exchangeError;
2103
+ try {
2104
+ const response = await client.fetch(PROXY_AUTH_DIAGNOSTIC_URL, {
2105
+ method: "GET",
2106
+ timeout: PROXY_AUTH_DIAGNOSTIC_TIMEOUT_MS,
2107
+ ...(clientOptions.signal ? { signal: clientOptions.signal } : {}),
2108
+ });
2109
+ status = response.status;
2110
+ const normalized = await normalizeResponseWithSignal(response, undefined, undefined, clientOptions.signal);
2111
+ return classifyProxyAuthDiagnosticMessage(normalized.body);
2112
+ }
2113
+ catch (error) {
2114
+ exchangeError = error;
2115
+ throw error;
2116
+ }
2117
+ finally {
2118
+ finishExchange(status, exchangeError);
2119
+ }
1977
2120
  }, clientOptions.signal);
1978
2121
  }
1979
2122
  catch (error) {