@apifuse/provider-sdk 2.2.0-beta.2 → 2.2.0-beta.21

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 (291) hide show
  1. package/AUTHORING.md +487 -0
  2. package/CHANGELOG.md +90 -0
  3. package/README.md +49 -3
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +14 -0
  9. package/bin/apifuse-pack-smoke.ts +36 -81
  10. package/bin/apifuse-pack-types.ts +305 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +659 -111
  13. package/bin/apifuse-submit-check.ts +546 -48
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth.d.ts +2 -2
  21. package/dist/auth.js +9 -18
  22. package/dist/ceremonies/index.d.ts +9 -1
  23. package/dist/ceremonies/index.js +65 -18
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/config/loader.d.ts +176 -8
  32. package/dist/config/loader.js +424 -95
  33. package/dist/contract-serialization.d.ts +2 -2
  34. package/dist/contract-serialization.js +3 -6
  35. package/dist/contract-types.d.ts +2 -2
  36. package/dist/contract.d.ts +3 -3
  37. package/dist/contract.js +4 -6
  38. package/dist/define.d.ts +9 -1
  39. package/dist/define.js +304 -119
  40. package/dist/dev.d.ts +1 -1
  41. package/dist/dev.js +1 -1
  42. package/dist/error-resolution.d.ts +2 -0
  43. package/dist/error-resolution.js +90 -0
  44. package/dist/errors.d.ts +22 -1
  45. package/dist/errors.js +88 -0
  46. package/dist/fixture-sanitization.d.ts +26 -0
  47. package/dist/fixture-sanitization.js +216 -0
  48. package/dist/i18n/catalog.d.ts +2 -2
  49. package/dist/i18n/catalog.js +4 -10
  50. package/dist/i18n/index.d.ts +2 -2
  51. package/dist/i18n/index.js +2 -2
  52. package/dist/i18n/keys.d.ts +2 -2
  53. package/dist/index.d.ts +46 -42
  54. package/dist/index.js +41 -37
  55. package/dist/lint.d.ts +1 -1
  56. package/dist/lint.js +8 -15
  57. package/dist/native-address.d.ts +43 -0
  58. package/dist/native-address.js +281 -0
  59. package/dist/native-egress-policy.d.ts +31 -0
  60. package/dist/native-egress-policy.js +288 -0
  61. package/dist/observability.d.ts +5 -2
  62. package/dist/observability.js +48 -1
  63. package/dist/provider.d.ts +12 -11
  64. package/dist/provider.js +10 -9
  65. package/dist/public-schema-field-lint.d.ts +1 -1
  66. package/dist/recipes/gov-api.js +1 -1
  67. package/dist/runtime/auth-flow.d.ts +2 -1
  68. package/dist/runtime/auth-flow.js +4 -3
  69. package/dist/runtime/browser.d.ts +1 -1
  70. package/dist/runtime/browser.js +15 -29
  71. package/dist/runtime/cache.d.ts +1 -1
  72. package/dist/runtime/cache.js +4 -8
  73. package/dist/runtime/choice.d.ts +1 -1
  74. package/dist/runtime/choice.js +31 -35
  75. package/dist/runtime/credential.d.ts +1 -1
  76. package/dist/runtime/credential.js +1 -1
  77. package/dist/runtime/env.d.ts +1 -1
  78. package/dist/runtime/executor.d.ts +1 -1
  79. package/dist/runtime/executor.js +31 -4
  80. package/dist/runtime/http.d.ts +2 -2
  81. package/dist/runtime/http.js +387 -47
  82. package/dist/runtime/insights.d.ts +1 -1
  83. package/dist/runtime/insights.js +6 -13
  84. package/dist/runtime/instrumentation.d.ts +2 -2
  85. package/dist/runtime/instrumentation.js +345 -22
  86. package/dist/runtime/keyring.js +1 -1
  87. package/dist/runtime/namespace.js +1 -1
  88. package/dist/runtime/native-network.d.ts +127 -0
  89. package/dist/runtime/native-network.js +1298 -0
  90. package/dist/runtime/otlp.d.ts +1 -1
  91. package/dist/runtime/perf.d.ts +1 -1
  92. package/dist/runtime/provider.d.ts +1 -1
  93. package/dist/runtime/provider.js +1 -2
  94. package/dist/runtime/proxy-errors.d.ts +1 -1
  95. package/dist/runtime/proxy-errors.js +9 -7
  96. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  97. package/dist/runtime/proxy-nodemaven.js +146 -0
  98. package/dist/runtime/proxy-retry-policy.d.ts +2 -2
  99. package/dist/runtime/proxy-retry-policy.js +2 -2
  100. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  101. package/dist/runtime/proxy-telemetry.js +55 -52
  102. package/dist/runtime/redirects.d.ts +29 -0
  103. package/dist/runtime/redirects.js +36 -0
  104. package/dist/runtime/redis.d.ts +1 -1
  105. package/dist/runtime/redis.js +2 -4
  106. package/dist/runtime/request-options.d.ts +68 -1
  107. package/dist/runtime/request-options.js +548 -0
  108. package/dist/runtime/secrets.d.ts +27 -0
  109. package/dist/runtime/secrets.js +51 -0
  110. package/dist/runtime/state.d.ts +2 -2
  111. package/dist/runtime/state.js +238 -26
  112. package/dist/runtime/stealth.d.ts +5 -3
  113. package/dist/runtime/stealth.js +423 -143
  114. package/dist/runtime/stt.d.ts +1 -1
  115. package/dist/runtime/stt.js +11 -15
  116. package/dist/runtime/trace.d.ts +2 -2
  117. package/dist/runtime/trace.js +2 -4
  118. package/dist/runtime/waterfall.d.ts +1 -1
  119. package/dist/schema.d.ts +1 -1
  120. package/dist/schema.js +7 -15
  121. package/dist/serve.d.ts +1 -1
  122. package/dist/serve.js +1 -1
  123. package/dist/server/index.d.ts +7 -7
  124. package/dist/server/index.js +6 -6
  125. package/dist/server/self-test-input-tokens.d.ts +2 -1
  126. package/dist/server/self-test-input-tokens.js +18 -14
  127. package/dist/server/self-test-redaction.d.ts +1 -1
  128. package/dist/server/self-test-redaction.js +1 -1
  129. package/dist/server/self-test.d.ts +104 -3
  130. package/dist/server/self-test.js +673 -115
  131. package/dist/server/serve.d.ts +116 -4
  132. package/dist/server/serve.js +799 -128
  133. package/dist/server/types.d.ts +34 -9
  134. package/dist/server/types.js +8 -1
  135. package/dist/stateful/errors.d.ts +14 -0
  136. package/dist/stateful/errors.js +14 -0
  137. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  138. package/dist/stateful/http-provider-event-emitter.js +237 -0
  139. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  140. package/dist/stateful/http-session-owner-registry.js +210 -0
  141. package/dist/stateful/index.d.ts +18 -0
  142. package/dist/stateful/index.js +18 -0
  143. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  144. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  145. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  146. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  147. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  148. package/dist/stateful/provider-event-pipeline.js +1 -0
  149. package/dist/stateful/provider-events.d.ts +101 -0
  150. package/dist/stateful/provider-events.js +289 -0
  151. package/dist/stateful/session-key.d.ts +15 -0
  152. package/dist/stateful/session-key.js +86 -0
  153. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  154. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  155. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  156. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  157. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  158. package/dist/stateful/stateful-provider-adapter.js +287 -0
  159. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  160. package/dist/stateful/stateful-provider-observability.js +161 -0
  161. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  162. package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
  163. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  164. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  165. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  166. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  167. package/dist/stateful/stateful-provider-session-routing.d.ts +71 -0
  168. package/dist/stateful/stateful-provider-session-routing.js +353 -0
  169. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  170. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  171. package/dist/stateful-signing.d.ts +18 -0
  172. package/dist/stateful-signing.js +27 -0
  173. package/dist/stealth/profiles.d.ts +1 -1
  174. package/dist/stealth/profiles.js +5 -14
  175. package/dist/stream-evidence.d.ts +74 -0
  176. package/dist/stream-evidence.js +785 -0
  177. package/dist/stream.d.ts +1 -1
  178. package/dist/testing/index.d.ts +2 -2
  179. package/dist/testing/index.js +2 -2
  180. package/dist/testing/run.d.ts +32 -2
  181. package/dist/testing/run.js +478 -28
  182. package/dist/types.d.ts +342 -14
  183. package/dist/types.js +1 -0
  184. package/dist/user-input.d.ts +30 -0
  185. package/dist/user-input.js +66 -0
  186. package/package.json +16 -5
  187. package/src/auth-turn/index.ts +1 -1
  188. package/src/auth.ts +28 -86
  189. package/src/ceremonies/index.ts +103 -78
  190. package/src/cli/commands.ts +10 -0
  191. package/src/cli/create.ts +42 -35
  192. package/src/cli/prompt-assets.ts +865 -0
  193. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  194. package/src/config/loader.ts +652 -204
  195. package/src/contract-serialization.ts +5 -11
  196. package/src/contract-types.ts +2 -2
  197. package/src/contract.ts +12 -28
  198. package/src/define.ts +499 -498
  199. package/src/dev.ts +4 -9
  200. package/src/error-resolution.ts +91 -0
  201. package/src/errors.ts +110 -5
  202. package/src/fixture-sanitization.ts +247 -0
  203. package/src/i18n/catalog.ts +10 -32
  204. package/src/i18n/index.ts +2 -2
  205. package/src/i18n/keys.ts +5 -11
  206. package/src/index.ts +112 -42
  207. package/src/lint.ts +88 -152
  208. package/src/native-address.ts +340 -0
  209. package/src/native-egress-policy.ts +358 -0
  210. package/src/observability.ts +51 -1
  211. package/src/provider.ts +66 -11
  212. package/src/public-schema-field-lint.ts +7 -33
  213. package/src/recipes/gov-api.ts +2 -5
  214. package/src/runtime/auth-flow.ts +7 -7
  215. package/src/runtime/browser.ts +64 -187
  216. package/src/runtime/cache.ts +20 -67
  217. package/src/runtime/choice.ts +79 -132
  218. package/src/runtime/credential.ts +2 -2
  219. package/src/runtime/env.ts +1 -1
  220. package/src/runtime/executor.ts +43 -20
  221. package/src/runtime/http.ts +494 -57
  222. package/src/runtime/insights.ts +15 -53
  223. package/src/runtime/instrumentation.ts +495 -66
  224. package/src/runtime/keyring.ts +7 -19
  225. package/src/runtime/namespace.ts +2 -7
  226. package/src/runtime/native-network.ts +1686 -0
  227. package/src/runtime/otlp.ts +12 -23
  228. package/src/runtime/perf.ts +1 -1
  229. package/src/runtime/provider.ts +4 -9
  230. package/src/runtime/proxy-errors.ts +29 -42
  231. package/src/runtime/proxy-nodemaven.ts +221 -0
  232. package/src/runtime/proxy-retry-policy.ts +3 -3
  233. package/src/runtime/proxy-telemetry.ts +79 -77
  234. package/src/runtime/redirects.ts +66 -0
  235. package/src/runtime/redis.ts +4 -12
  236. package/src/runtime/request-options.ts +679 -9
  237. package/src/runtime/secrets.ts +64 -0
  238. package/src/runtime/state.ts +353 -133
  239. package/src/runtime/stealth.ts +505 -154
  240. package/src/runtime/stt.ts +38 -94
  241. package/src/runtime/trace.ts +14 -44
  242. package/src/runtime/waterfall.ts +5 -18
  243. package/src/schema.ts +23 -84
  244. package/src/serve.ts +1 -1
  245. package/src/server/index.ts +29 -7
  246. package/src/server/self-test-input-tokens.ts +29 -14
  247. package/src/server/self-test-redaction.ts +2 -2
  248. package/src/server/self-test.ts +857 -132
  249. package/src/server/serve.ts +1151 -328
  250. package/src/server/types.ts +12 -13
  251. package/src/stateful/README.md +146 -0
  252. package/src/stateful/errors.ts +23 -0
  253. package/src/stateful/http-provider-event-emitter.ts +314 -0
  254. package/src/stateful/http-session-owner-registry.ts +306 -0
  255. package/src/stateful/index.ts +18 -0
  256. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  257. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  258. package/src/stateful/provider-event-pipeline.ts +61 -0
  259. package/src/stateful/provider-events.ts +462 -0
  260. package/src/stateful/session-key.ts +111 -0
  261. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  262. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  263. package/src/stateful/stateful-provider-adapter.ts +562 -0
  264. package/src/stateful/stateful-provider-observability.ts +261 -0
  265. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  266. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  267. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  268. package/src/stateful/stateful-provider-session-routing.ts +555 -0
  269. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  270. package/src/stateful-signing.ts +46 -0
  271. package/src/stealth/profiles.ts +10 -26
  272. package/src/stream-evidence.ts +988 -0
  273. package/src/stream.ts +8 -19
  274. package/src/testing/index.ts +10 -2
  275. package/src/testing/run.ts +653 -74
  276. package/src/types.ts +408 -28
  277. package/src/user-input.ts +118 -0
  278. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  279. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  280. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  281. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  282. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  283. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  284. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  285. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  286. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  287. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  288. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  289. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  290. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  291. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -1,12 +1,16 @@
1
- import { resolveProxyConfigAsync } from "../config/loader";
2
- import { ProviderError, TransportError } from "../errors";
3
- import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream";
4
- import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, isProxyTransportRetryMethod, normalizeProxyTransportRetryOptions, proxyTransportRetryErrorCode, proxyTransportRetryErrorStatus, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy";
5
- import { appendQueryParams, normalizeHttpRequestBody } from "./request-options";
1
+ import { policyRotatesTransportVendorChain, resolvePolicyTransportAttemptCap, resolveProxyConfigAsync, } from "../config/loader.js";
2
+ import { HttpRedirectError, ProviderError, TransportError } from "../errors.js";
3
+ import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream.js";
4
+ import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, isProxyTransportRetryMethod, normalizeProxyTransportRetryOptions, proxyTransportRetryErrorCode, proxyTransportRetryErrorStatus, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy.js";
5
+ import { evaluateRedirectHop, isRedirectStatus, resolveRedirectUrl } from "./redirects.js";
6
+ import { normalizeHttpRequestBody, redactSensitiveError, redactSensitiveRequestError, serializeRequestUrl, } from "./request-options.js";
6
7
  const DEFAULT_HTTP_BASE_URL = "http://localhost";
7
8
  function isHttpStatusOutcome(outcome) {
8
9
  return "kind" in outcome && outcome.kind === "http-status";
9
10
  }
11
+ function isDedupeSkipOutcome(outcome) {
12
+ return "kind" in outcome && outcome.kind === "dedupe-skip";
13
+ }
10
14
  async function sleep(ms) {
11
15
  if (ms <= 0)
12
16
  return;
@@ -121,9 +125,38 @@ function requireNativeResponseBody(response) {
121
125
  }
122
126
  return response.body;
123
127
  }
124
- function toNativeHttpStreamResponse(response) {
128
+ function sanitizeStreamErrors(body, serializedUrl) {
129
+ if (serializedUrl.sensitiveValues.length === 0)
130
+ return body;
131
+ let reader;
132
+ return new ReadableStream({
133
+ async pull(controller) {
134
+ try {
135
+ reader ??= body.getReader();
136
+ const chunk = await reader.read();
137
+ if (chunk.done) {
138
+ controller.close();
139
+ return;
140
+ }
141
+ controller.enqueue(chunk.value);
142
+ }
143
+ catch (error) {
144
+ controller.error(redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl));
145
+ }
146
+ },
147
+ async cancel(reason) {
148
+ try {
149
+ await (reader ? reader.cancel(reason) : body.cancel(reason));
150
+ }
151
+ catch (error) {
152
+ throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
153
+ }
154
+ },
155
+ }, { highWaterMark: 0 });
156
+ }
157
+ function toNativeHttpStreamResponse(response, serializedUrl) {
125
158
  const headers = Object.fromEntries(response.headers.entries());
126
- const body = requireNativeResponseBody(response);
159
+ const body = sanitizeStreamErrors(requireNativeResponseBody(response), serializedUrl);
127
160
  return {
128
161
  body,
129
162
  headers,
@@ -164,6 +197,181 @@ function isAbsoluteUrl(url) {
164
197
  function resolveHttpUrl(baseUrl, url) {
165
198
  return new URL(url, baseUrl ?? DEFAULT_HTTP_BASE_URL).toString();
166
199
  }
200
+ const MAX_HTTP_REDIRECT_HOPS = 20;
201
+ const HTTP_REDIRECT_POLICY_FIELDS = new Set(["mode", "maxHops"]);
202
+ const REDIRECT_BODY_HEADERS = new Set([
203
+ "content-encoding",
204
+ "content-language",
205
+ "content-location",
206
+ "content-type",
207
+ ]);
208
+ const MALFORMED_REDIRECT_TARGET = "[malformed redirect target]";
209
+ function invalidHttpRedirectPolicy(message, cause) {
210
+ return new TransportError(`Invalid ctx.http redirectPolicy: ${message}`, {
211
+ code: "http_redirect_policy_invalid",
212
+ ...(cause ? { cause } : {}),
213
+ });
214
+ }
215
+ /** Snapshot untrusted caller input synchronously, before proxy resolution or fetch. */
216
+ function normalizeHttpRedirectPolicy(value) {
217
+ if (value === undefined)
218
+ return undefined;
219
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
220
+ throw invalidHttpRedirectPolicy("expected an object");
221
+ }
222
+ try {
223
+ const keys = Reflect.ownKeys(value);
224
+ for (const key of keys) {
225
+ if (typeof key !== "string" || !HTTP_REDIRECT_POLICY_FIELDS.has(key)) {
226
+ throw invalidHttpRedirectPolicy(`unknown field ${String(key)}`);
227
+ }
228
+ }
229
+ for (const field of HTTP_REDIRECT_POLICY_FIELDS) {
230
+ const descriptor = Object.getOwnPropertyDescriptor(value, field);
231
+ if (!descriptor || !("value" in descriptor)) {
232
+ throw invalidHttpRedirectPolicy(`${field} must be an own data property`);
233
+ }
234
+ }
235
+ const record = value;
236
+ if (record.mode !== "same-origin") {
237
+ throw invalidHttpRedirectPolicy('mode must be "same-origin"');
238
+ }
239
+ if (typeof record.maxHops !== "number" ||
240
+ !Number.isInteger(record.maxHops) ||
241
+ record.maxHops < 0 ||
242
+ record.maxHops > MAX_HTTP_REDIRECT_HOPS) {
243
+ throw invalidHttpRedirectPolicy(`maxHops must be an integer from 0 to ${MAX_HTTP_REDIRECT_HOPS}`);
244
+ }
245
+ return { mode: "same-origin", maxHops: record.maxHops };
246
+ }
247
+ catch (error) {
248
+ if (error instanceof TransportError)
249
+ throw error;
250
+ throw invalidHttpRedirectPolicy("could not be inspected safely", error instanceof Error ? error : undefined);
251
+ }
252
+ }
253
+ function snapshotHttpRedirectPolicy(options) {
254
+ try {
255
+ return normalizeHttpRedirectPolicy(options.redirectPolicy);
256
+ }
257
+ catch (error) {
258
+ if (error instanceof TransportError)
259
+ throw error;
260
+ throw invalidHttpRedirectPolicy("could not be read safely", error instanceof Error ? error : undefined);
261
+ }
262
+ }
263
+ function withoutRedirectBodyHeaders(headers) {
264
+ const nextHeaders = new Headers(headers);
265
+ for (const name of REDIRECT_BODY_HEADERS)
266
+ nextHeaders.delete(name);
267
+ return nextHeaders;
268
+ }
269
+ function redirectDiagnosticTarget(value) {
270
+ try {
271
+ const parsed = new URL(value);
272
+ // Origin omits URL userinfo. Keeping only origin + path makes diagnostics
273
+ // useful while structurally excluding every query value and fragment,
274
+ // including attacker-chosen keys the provider did not declare sensitive.
275
+ const redactedQuery = parsed.search ? "?[REDACTED]" : "";
276
+ return parsed.origin === "null"
277
+ ? `${parsed.protocol}<opaque-target>`
278
+ : `${parsed.origin}${parsed.pathname}${redactedQuery}`;
279
+ }
280
+ catch {
281
+ return MALFORMED_REDIRECT_TARGET;
282
+ }
283
+ }
284
+ function discardRedirectResponseBody(response) {
285
+ try {
286
+ const cancellation = response.body?.cancel();
287
+ if (cancellation)
288
+ void cancellation.catch(() => undefined);
289
+ }
290
+ catch {
291
+ // The redirect decision is security-significant and must not be replaced
292
+ // or delayed by an upstream body's cancellation failure. Cancellation was
293
+ // attempted; redirect evaluation continues without awaiting its completion.
294
+ }
295
+ }
296
+ async function fetchWithHttpRedirectPolicy(requestUrl, requestInit, policy) {
297
+ if (!policy)
298
+ return fetch(requestUrl, requestInit);
299
+ const initialUrl = new URL(requestUrl);
300
+ if (initialUrl.protocol !== "http:" && initialUrl.protocol !== "https:") {
301
+ throw new TransportError("ctx.http redirectPolicy requires an HTTP(S) origin", {
302
+ code: "transport_invalid_url",
303
+ });
304
+ }
305
+ const initialOrigin = initialUrl.origin;
306
+ let currentUrl = requestUrl;
307
+ let method = normalizeHttpMethod(requestInit.method ?? "GET");
308
+ let body = requestInit.body;
309
+ let headers = requestInit.headers;
310
+ let followedHops = 0;
311
+ const visitedRequests = new Set([`${method} ${currentUrl}`]);
312
+ while (true) {
313
+ const response = await fetch(currentUrl, {
314
+ ...requestInit,
315
+ body,
316
+ headers,
317
+ method,
318
+ redirect: "manual",
319
+ });
320
+ if (!isRedirectStatus(response.status))
321
+ return response;
322
+ const location = response.headers.get("location");
323
+ discardRedirectResponseBody(response);
324
+ let nextUrlString;
325
+ try {
326
+ nextUrlString = resolveRedirectUrl(location || undefined, currentUrl);
327
+ }
328
+ catch {
329
+ const target = MALFORMED_REDIRECT_TARGET;
330
+ throw new HttpRedirectError(`Redirect response has malformed Location target ${target}`, {
331
+ reason: "missing_location",
332
+ target,
333
+ status: response.status,
334
+ });
335
+ }
336
+ const decision = evaluateRedirectHop({
337
+ status: response.status,
338
+ method,
339
+ nextUrl: nextUrlString,
340
+ shouldStop: nextUrlString ? new URL(nextUrlString).origin !== initialOrigin : false,
341
+ redirectCount: followedHops + 1,
342
+ maxHops: policy.maxHops,
343
+ visitedRequests,
344
+ });
345
+ if (decision.kind === "stop") {
346
+ const target = decision.nextUrl ? redirectDiagnosticTarget(decision.nextUrl) : undefined;
347
+ const message = (() => {
348
+ switch (decision.reason) {
349
+ case "stopped":
350
+ return `Redirect policy refused cross-origin target ${target}`;
351
+ case "max_hops":
352
+ return `Redirect policy reached maxHops before target ${target}`;
353
+ case "loop":
354
+ return `Redirect loop refused target ${target}`;
355
+ case "missing_location":
356
+ return `Redirect response from ${redirectDiagnosticTarget(currentUrl)} is missing Location`;
357
+ }
358
+ })();
359
+ throw new HttpRedirectError(message, {
360
+ reason: decision.reason,
361
+ ...(target ? { target } : {}),
362
+ status: response.status,
363
+ });
364
+ }
365
+ if (decision.nextMethod !== method) {
366
+ body = undefined;
367
+ headers = withoutRedirectBodyHeaders(headers);
368
+ }
369
+ method = decision.nextMethod;
370
+ currentUrl = decision.nextUrl;
371
+ followedHops += 1;
372
+ visitedRequests.add(`${method} ${currentUrl}`);
373
+ }
374
+ }
167
375
  async function resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset = 0) {
168
376
  const resolvedProxy = await resolveProxyConfigAsync({
169
377
  proxy: options.proxy ?? clientOptions.proxy,
@@ -175,6 +383,9 @@ async function resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffs
175
383
  baseProxyAttempt: clientOptions.proxyAttempt,
176
384
  retryAttemptOffset: proxyAttemptOffset,
177
385
  }),
386
+ // Bun's native fetch proxy option tunnels HTTP CONNECT only; SOCKS5 is not
387
+ // supported here, so a socks5 policy fails loudly rather than downgrading.
388
+ transportProtocols: ["http"],
178
389
  telemetry: clientOptions.telemetry,
179
390
  });
180
391
  if (resolvedProxy.shouldWarn) {
@@ -196,15 +407,37 @@ function normalizeNativeFetchBody(body) {
196
407
  copied.set(normalized);
197
408
  return copied.buffer;
198
409
  }
199
- async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, warn, statusRetryCodes, proxyAttemptOffset = 0) {
200
- const requestUrl = appendQueryParams(resolveHttpUrl(baseUrl, url), options.params);
410
+ function serializeHttpRequestUrl(baseUrl, url, options) {
411
+ try {
412
+ return serializeRequestUrl(resolveHttpUrl(baseUrl, url), options.params, options.sensitiveParams);
413
+ }
414
+ catch (error) {
415
+ throw redactSensitiveRequestError(error, url, options.sensitiveParams);
416
+ }
417
+ }
418
+ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, warn, statusRetryCodes, proxyAttemptOffset = 0, dedupe) {
419
+ const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
420
+ const { requestUrl } = serializedUrl;
201
421
  const controller = options.timeout ? new AbortController() : undefined;
202
422
  const timeoutHandle = options.timeout
203
423
  ? setTimeout(() => controller?.abort(), options.timeout)
204
424
  : undefined;
205
425
  let proxy;
206
426
  try {
427
+ // Resolve inside the try (and after the timeout is armed) so allocator
428
+ // failures are branded as TransportErrors and count against the request
429
+ // deadline, exactly as an inline resolve would.
207
430
  proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
431
+ // For a registry allocator chain, skip an endpoint a prior attempt already
432
+ // tried rather than re-issuing the same request. Returning the sentinel
433
+ // (instead of breaking) lets the loop keep advancing the flat offset until
434
+ // it crosses into the fallback vendor's pool span.
435
+ if (dedupe && proxy) {
436
+ if (dedupe.attempted.has(proxy)) {
437
+ return { kind: "dedupe-skip" };
438
+ }
439
+ dedupe.attempted.add(proxy);
440
+ }
208
441
  const requestInit = {
209
442
  headers: options.headers,
210
443
  method,
@@ -214,9 +447,7 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
214
447
  if (options.body !== undefined) {
215
448
  requestInit.body = normalizeNativeFetchBody(options.body);
216
449
  }
217
- const response = await fetch(requestUrl, {
218
- ...requestInit,
219
- });
450
+ const response = await fetchWithHttpRedirectPolicy(requestUrl, requestInit, options.redirectPolicy);
220
451
  const headers = Object.fromEntries(response.headers.entries());
221
452
  if (statusRetryCodes && response.status >= 400) {
222
453
  await drainNativeResponseBody(response);
@@ -239,9 +470,9 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
239
470
  }
240
471
  catch (error) {
241
472
  if (error instanceof SyntaxError) {
242
- throw error;
473
+ throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
243
474
  }
244
- const transportError = toHttpTransportError(error);
475
+ const transportError = redactSensitiveError(toHttpTransportError(error), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
245
476
  transportError.proxyUsed = Boolean(proxy);
246
477
  throw transportError;
247
478
  }
@@ -251,7 +482,8 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
251
482
  }
252
483
  }
253
484
  async function fetchNativeHttpStream(baseUrl, url, method, options, clientOptions, warn) {
254
- const requestUrl = appendQueryParams(resolveHttpUrl(baseUrl, url), options.params);
485
+ const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
486
+ const { requestUrl } = serializedUrl;
255
487
  const controller = options.timeout ? new AbortController() : undefined;
256
488
  const timeoutHandle = options.timeout
257
489
  ? setTimeout(() => controller?.abort(), options.timeout)
@@ -267,9 +499,7 @@ async function fetchNativeHttpStream(baseUrl, url, method, options, clientOption
267
499
  if (options.body !== undefined) {
268
500
  requestInit.body = normalizeNativeFetchBody(options.body);
269
501
  }
270
- const response = await fetch(requestUrl, {
271
- ...requestInit,
272
- });
502
+ const response = await fetchWithHttpRedirectPolicy(requestUrl, requestInit, options.redirectPolicy);
273
503
  if (response.status >= 400 && options.throwOnHttpError !== false) {
274
504
  await drainNativeResponseBody(response);
275
505
  throw new TransportError(`Upstream request failed with status ${response.status}`, {
@@ -277,13 +507,13 @@ async function fetchNativeHttpStream(baseUrl, url, method, options, clientOption
277
507
  status: response.status,
278
508
  });
279
509
  }
280
- return toNativeHttpStreamResponse(response);
510
+ return toNativeHttpStreamResponse(response, serializedUrl);
281
511
  }
282
512
  catch (error) {
283
513
  if (error instanceof SyntaxError) {
284
- throw error;
514
+ throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
285
515
  }
286
- throw toHttpTransportError(error);
516
+ throw redactSensitiveError(toHttpTransportError(error), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
287
517
  }
288
518
  finally {
289
519
  if (timeoutHandle)
@@ -301,19 +531,31 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
301
531
  warn(message);
302
532
  };
303
533
  async function request(url, method, options = {}) {
304
- if (!baseUrl && !isAbsoluteUrl(url)) {
305
- throw new TransportError("ctx.http requires an absolute URL when provider.upstream.baseUrl is not declared", { code: "transport_invalid_url" });
306
- }
307
- assertNoHttpTransportOverrides(options);
308
- const headersOptions = withClientHeaders(options, clientOptions, options.body);
309
- const methodName = normalizeHttpMethod(method);
310
- const explicitRetry = headersOptions.retry !== undefined;
311
- const retryOptions = normalizeProxyTransportRetryOptions(headersOptions.retry, {
312
- label: "HTTP",
313
- }) ??
314
- (explicitRetry ? undefined : createDefaultProxyTransportRetryOptions({ label: "HTTP" }));
315
- if (retryOptions)
316
- validateUnsafeProxyTransportRetryMethods(retryOptions, "HTTP");
534
+ const { explicitRetry, headersOptions, methodName, retryOptions } = (() => {
535
+ try {
536
+ if (!baseUrl && !isAbsoluteUrl(url)) {
537
+ throw new TransportError("ctx.http requires an absolute URL when provider.upstream.baseUrl is not declared", { code: "transport_invalid_url" });
538
+ }
539
+ assertNoHttpTransportOverrides(options);
540
+ const redirectPolicy = snapshotHttpRedirectPolicy(options);
541
+ const headersOptions = {
542
+ ...withClientHeaders(options, clientOptions, options.body),
543
+ redirectPolicy,
544
+ };
545
+ const methodName = normalizeHttpMethod(method);
546
+ const explicitRetry = headersOptions.retry !== undefined;
547
+ const retryOptions = normalizeProxyTransportRetryOptions(headersOptions.retry, {
548
+ label: "HTTP",
549
+ }) ??
550
+ (explicitRetry ? undefined : createDefaultProxyTransportRetryOptions({ label: "HTTP" }));
551
+ if (retryOptions)
552
+ validateUnsafeProxyTransportRetryMethods(retryOptions, "HTTP");
553
+ return { explicitRetry, headersOptions, methodName, retryOptions };
554
+ }
555
+ catch (error) {
556
+ throw redactSensitiveRequestError(error, url, options.sensitiveParams);
557
+ }
558
+ })();
317
559
  const retryEnabled = Boolean(retryOptions &&
318
560
  retryOptions.attempts > 1 &&
319
561
  isProxyTransportRetryMethod(methodName, retryOptions));
@@ -325,22 +567,97 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
325
567
  const attemptOptions = statusRetryEnabled
326
568
  ? { ...headersOptions, throwOnHttpError: false }
327
569
  : headersOptions;
328
- const executeOnce = (proxyAttemptOffset = 0) => fetchNativeHttp(baseUrl, url, methodName, attemptOptions, clientOptions, warnOnce, statusRetryEnabled ? retryOptions?.statusCodes : undefined, proxyAttemptOffset);
570
+ // Span the whole vendor chain on transport failures. Like ctx.stealth, a
571
+ // policy-managed proxy resolves a *different* endpoint/vendor per attempt
572
+ // (the flat proxyAttemptOffset rotates across the concatenated vendor pool
573
+ // spans), so a transport failure should advance to the next endpoint —
574
+ // potentially crossing into the fallback vendor — rather than stopping at
575
+ // the per-endpoint retry budget and stranding the request on the primary
576
+ // vendor. resolvePolicyTransportAttemptCap widens the cap to the chain span
577
+ // only for implicit, safe-method allocator requests; explicit retry
578
+ // policies (their documented `attempts` ceiling), unsafe methods, and
579
+ // static/non-registry vendors keep the retry budget. Status-code retries
580
+ // stay bounded by the retry budget regardless; only transport rotation gets
581
+ // the full span.
582
+ const policyProxy = (() => {
583
+ const policy = clientOptions.proxyPolicy ?? clientOptions.upstream?.proxy;
584
+ return policy && typeof policy === "object" ? policy : undefined;
585
+ })();
586
+ const usesPolicyAllocator = Boolean(policyProxy) && !options.proxy && !clientOptions.proxy;
587
+ const transportAttemptCap = retryOptions
588
+ ? resolvePolicyTransportAttemptCap({
589
+ policy: policyProxy,
590
+ usesPolicyAllocator,
591
+ retryAttempts: retryOptions.attempts,
592
+ explicitRetry,
593
+ method: methodName,
594
+ })
595
+ : 1;
596
+ // Track resolved endpoints across a policy-allocator chain. Successive
597
+ // attempts rotate the flat offset across the concatenated vendor pool
598
+ // spans, but an under-filled allocation (fewer live endpoints than the
599
+ // configured pool size) makes the modulo mapping repeat endpoints before
600
+ // the offset reaches the next vendor. Rather than re-hammering an
601
+ // already-tried endpoint under backoff, fetchNativeHttp returns a skip
602
+ // sentinel for a duplicate; the loop then advances the flat offset without
603
+ // issuing the request, so it keeps walking toward — and into — the fallback
604
+ // vendor's pool span instead of stalling on the primary vendor.
605
+ // De-duplication is gated on the SAME predicate that widens the attempt cap
606
+ // (implicit, safe-method, registry-chain rotation). It must NOT engage for
607
+ // an explicit retry policy: there the caller's `attempts` count is the
608
+ // contract and each attempt must issue against whatever endpoint it resolves
609
+ // — even a repeat — instead of being silently skipped (which would collapse
610
+ // a `poolSize: 1` + `attempts: 3` request to a single fetch).
611
+ const dedupeAllocatorEndpoints = policyRotatesTransportVendorChain({
612
+ policy: policyProxy,
613
+ usesPolicyAllocator,
614
+ explicitRetry,
615
+ method: methodName,
616
+ });
617
+ const dedupeContext = dedupeAllocatorEndpoints ? { attempted: new Set() } : undefined;
618
+ const executeOnce = (proxyAttemptOffset = 0) => fetchNativeHttp(baseUrl, url, methodName, attemptOptions, clientOptions, warnOnce, statusRetryEnabled ? retryOptions?.statusCodes : undefined, proxyAttemptOffset, dedupeContext);
329
619
  if (!retryEnabled || !retryOptions) {
330
620
  const outcome = await executeOnce();
621
+ if (isDedupeSkipOutcome(outcome)) {
622
+ // Single-shot path never de-duplicates (dedupeContext is undefined),
623
+ // but keep the union total.
624
+ throw new TransportError("HTTP request produced no terminal result", {
625
+ code: "retry_exhausted",
626
+ });
627
+ }
331
628
  if (isHttpStatusOutcome(outcome)) {
332
629
  throw toUpstreamHttpError(outcome.status);
333
630
  }
334
631
  return outcome;
335
632
  }
633
+ let lastError;
336
634
  let lastErrorCode;
337
635
  let lastStatus;
338
- for (let attempt = 1; attempt <= retryOptions.attempts; attempt += 1) {
636
+ // `attempt` walks the flat proxy offset across the full chain span; `issued`
637
+ // counts requests that were actually sent (skipped duplicate offsets do not
638
+ // increment it). Retry summaries and the status-retry budget must reflect
639
+ // issued requests, not the raw offset, so they stay accurate when partial
640
+ // allocations skip offsets.
641
+ let issued = 0;
642
+ for (let attempt = 1; attempt <= transportAttemptCap; attempt += 1) {
643
+ // Whether this offset actually issued a request (vs. a skipped duplicate),
644
+ // so the catch counts a thrown *transport* failure once without
645
+ // double-counting a status outcome that already incremented before it
646
+ // re-threw as an upstream HTTP error.
647
+ let issuedThisAttempt = false;
339
648
  try {
340
649
  const outcome = await executeOnce(attempt - 1);
650
+ if (isDedupeSkipOutcome(outcome)) {
651
+ // Duplicate endpoint from a partial allocation: advance the flat
652
+ // offset without issuing the request (no backoff, not a failure) so
653
+ // the loop keeps rotating toward the fallback vendor.
654
+ continue;
655
+ }
656
+ issued += 1;
657
+ issuedThisAttempt = true;
341
658
  if (isHttpStatusOutcome(outcome)) {
342
659
  lastStatus = outcome.status;
343
- if (outcome.retryable && attempt < retryOptions.attempts) {
660
+ if (outcome.retryable && issued < retryOptions.attempts) {
344
661
  await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers));
345
662
  continue;
346
663
  }
@@ -350,10 +667,10 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
350
667
  if (response.status >= 400 && headersOptions.throwOnHttpError !== false) {
351
668
  throw toUpstreamHttpError(response.status);
352
669
  }
353
- if (attempt > 1) {
670
+ if (issued > 1) {
354
671
  const summary = {
355
- attempts: attempt,
356
- retries: attempt - 1,
672
+ attempts: issued,
673
+ retries: issued - 1,
357
674
  ...(retryOptions.preset ? { preset: retryOptions.preset } : {}),
358
675
  transport: "native",
359
676
  ...(lastErrorCode ? { lastErrorCode } : {}),
@@ -364,10 +681,13 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
364
681
  return response;
365
682
  }
366
683
  catch (error) {
684
+ if (!issuedThisAttempt)
685
+ issued += 1;
686
+ lastError = error;
367
687
  lastErrorCode = proxyTransportRetryErrorCode(error);
368
688
  lastStatus = proxyTransportRetryErrorStatus(error);
369
689
  const proxyUsed = Boolean(error.proxyUsed);
370
- if (attempt < retryOptions.attempts &&
690
+ if (attempt < transportAttemptCap &&
371
691
  shouldRetryProxyTransportAttempt({
372
692
  error,
373
693
  explicitRetry,
@@ -381,17 +701,37 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
381
701
  throw error;
382
702
  }
383
703
  }
704
+ // Reached when the attempt cap is consumed without a terminal outcome —
705
+ // e.g. the final offsets of a partial allocation all resolved to
706
+ // already-tried endpoints and were skipped. Surface the last real transport
707
+ // failure rather than a synthetic exhaustion error.
708
+ if (lastError !== undefined) {
709
+ throw lastError;
710
+ }
384
711
  throw new TransportError("HTTP retry exhausted without a terminal result", {
385
712
  code: "retry_exhausted",
386
713
  });
387
714
  }
388
715
  async function streamRequest(url, method, options = {}) {
389
- if (!baseUrl && !isAbsoluteUrl(url)) {
390
- throw new TransportError("ctx.http requires an absolute URL when provider.upstream.baseUrl is not declared", { code: "transport_invalid_url" });
391
- }
392
- assertNoHttpTransportOverrides(options);
393
- const headersOptions = withClientHeaders(options, clientOptions, options.body);
394
- const methodName = normalizeHttpMethod(method);
716
+ const { headersOptions, methodName } = (() => {
717
+ try {
718
+ if (!baseUrl && !isAbsoluteUrl(url)) {
719
+ throw new TransportError("ctx.http requires an absolute URL when provider.upstream.baseUrl is not declared", { code: "transport_invalid_url" });
720
+ }
721
+ assertNoHttpTransportOverrides(options);
722
+ const redirectPolicy = snapshotHttpRedirectPolicy(options);
723
+ return {
724
+ headersOptions: {
725
+ ...withClientHeaders(options, clientOptions, options.body),
726
+ redirectPolicy,
727
+ },
728
+ methodName: normalizeHttpMethod(method),
729
+ };
730
+ }
731
+ catch (error) {
732
+ throw redactSensitiveRequestError(error, url, options.sensitiveParams);
733
+ }
734
+ })();
395
735
  return fetchNativeHttpStream(baseUrl, url, methodName, headersOptions, clientOptions, warnOnce);
396
736
  }
397
737
  return {
@@ -1,4 +1,4 @@
1
- import type { Span } from "./trace";
1
+ import type { Span } from "./trace.js";
2
2
  export type InsightSeverity = "info" | "warning" | "error";
3
3
  export type Insight = {
4
4
  id: string;
@@ -1,4 +1,4 @@
1
- import { computePercentile } from "./perf";
1
+ import { computePercentile } from "./perf.js";
2
2
  const LARGE_RESPONSE_BYTES = 100_000;
3
3
  const SLOW_TRANSFORM_MS = 10;
4
4
  const DNS_WARN_MS = 5;
@@ -188,8 +188,7 @@ function getDnsRepeatedCandidate(spans) {
188
188
  if (entry.totalCount < 2) {
189
189
  continue;
190
190
  }
191
- const avgDnsMs = entry.dnsDurations.reduce((sum, value) => sum + value, 0) /
192
- entry.dnsDurations.length;
191
+ const avgDnsMs = entry.dnsDurations.reduce((sum, value) => sum + value, 0) / entry.dnsDurations.length;
193
192
  const reuseRate = entry.reuseCount / entry.totalCount;
194
193
  if (avgDnsMs > DNS_WARN_MS && reuseRate < 0.2) {
195
194
  if (!candidate || avgDnsMs > candidate.avgDnsMs) {
@@ -218,9 +217,7 @@ function getDnsRepeatedInsight(spans) {
218
217
  }
219
218
  function getProxyOverheadInsight(spans) {
220
219
  const requestSpans = spans.filter(isRequestSpan);
221
- const proxiedDurations = requestSpans
222
- .filter(hasProxy)
223
- .map((span) => span.duration_ms);
220
+ const proxiedDurations = requestSpans.filter(hasProxy).map((span) => span.duration_ms);
224
221
  const directDurations = requestSpans
225
222
  .filter((span) => !hasProxy(span))
226
223
  .map((span) => span.duration_ms);
@@ -230,10 +227,8 @@ function getProxyOverheadInsight(spans) {
230
227
  message: "✓ Proxy overhead: insufficient proxy/direct samples",
231
228
  };
232
229
  }
233
- const proxyAvg = proxiedDurations.reduce((sum, value) => sum + value, 0) /
234
- proxiedDurations.length;
235
- const directAvg = directDurations.reduce((sum, value) => sum + value, 0) /
236
- directDurations.length;
230
+ const proxyAvg = proxiedDurations.reduce((sum, value) => sum + value, 0) / proxiedDurations.length;
231
+ const directAvg = directDurations.reduce((sum, value) => sum + value, 0) / directDurations.length;
237
232
  if (directAvg > 0 && proxyAvg >= directAvg * 2) {
238
233
  return {
239
234
  triggered: true,
@@ -254,9 +249,7 @@ function getBrowserIdleInsight(spans) {
254
249
  if (waitMs !== undefined) {
255
250
  return waitMs;
256
251
  }
257
- return span.name.toLowerCase().includes("wait")
258
- ? span.duration_ms
259
- : undefined;
252
+ return span.name.toLowerCase().includes("wait") ? span.duration_ms : undefined;
260
253
  })
261
254
  .filter((value) => value !== undefined);
262
255
  if (waits.length === 0) {
@@ -1,5 +1,5 @@
1
- import type { ProviderContext } from "../types";
2
- import { type CreateTraceContextOptions, type TraceContext } from "./trace";
1
+ import type { ProviderContext } from "../types.js";
2
+ import { type CreateTraceContextOptions, type TraceContext } from "./trace.js";
3
3
  export interface InstrumentationOptions extends CreateTraceContextOptions {
4
4
  }
5
5
  export type InstrumentedProviderContext<T extends ProviderContext> = Omit<T, "trace"> & {