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

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 (243) hide show
  1. package/AUTHORING.md +268 -9
  2. package/CHANGELOG.md +4 -0
  3. package/README.md +1 -0
  4. package/bin/apifuse-check.ts +84 -2
  5. package/bin/apifuse-dev.ts +11 -31
  6. package/bin/apifuse-migrate-deployment.ts +200 -0
  7. package/bin/apifuse-pack-check.ts +11 -4
  8. package/bin/apifuse-pack-types.ts +187 -4
  9. package/bin/apifuse-record.ts +44 -32
  10. package/bin/apifuse-test.ts +3 -1
  11. package/dist/cli/commands.d.ts +1 -1
  12. package/dist/cli/commands.js +11 -0
  13. package/dist/cli/deployment-intent.d.ts +178 -0
  14. package/dist/cli/deployment-intent.js +277 -0
  15. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  16. package/dist/cli/migrate-deployment-intent.js +1552 -0
  17. package/dist/cli/migrate-provider-shape.d.ts +5 -1
  18. package/dist/cli/migrate-provider-shape.js +4 -2
  19. package/dist/cli/resolver-runtime.d.ts +21 -0
  20. package/dist/cli/resolver-runtime.js +31 -0
  21. package/dist/define.d.ts +3 -0
  22. package/dist/define.js +5 -0
  23. package/dist/dev.d.ts +3 -1
  24. package/dist/dev.js +6 -1
  25. package/dist/engine.d.ts +35 -1
  26. package/dist/engine.js +21 -2
  27. package/dist/error-resolution.d.ts +1 -0
  28. package/dist/error-resolution.js +54 -0
  29. package/dist/errors.d.ts +39 -1
  30. package/dist/errors.js +50 -0
  31. package/dist/i18n/error-messages.d.ts +86 -0
  32. package/dist/i18n/error-messages.js +192 -0
  33. package/dist/i18n/index.d.ts +1 -0
  34. package/dist/i18n/index.js +1 -0
  35. package/dist/i18n/keys.d.ts +12 -0
  36. package/dist/i18n/keys.js +76 -5
  37. package/dist/index.d.ts +7 -1
  38. package/dist/index.js +6 -0
  39. package/dist/lint.d.ts +10 -0
  40. package/dist/lint.js +412 -49
  41. package/dist/provider.d.ts +5 -5
  42. package/dist/provider.js +2 -2
  43. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  44. package/dist/runtime/browser-telemetry-binding.js +83 -0
  45. package/dist/runtime/browser-telemetry.d.ts +76 -0
  46. package/dist/runtime/browser-telemetry.js +165 -0
  47. package/dist/runtime/browser.d.ts +3 -0
  48. package/dist/runtime/browser.js +208 -47
  49. package/dist/runtime/cache-telemetry.d.ts +71 -0
  50. package/dist/runtime/cache-telemetry.js +177 -0
  51. package/dist/runtime/cache.js +67 -7
  52. package/dist/runtime/capability-response.d.ts +3 -0
  53. package/dist/runtime/capability-response.js +56 -0
  54. package/dist/runtime/capability-telemetry.d.ts +46 -0
  55. package/dist/runtime/capability-telemetry.js +68 -0
  56. package/dist/runtime/diagnostic-env.d.ts +3 -0
  57. package/dist/runtime/diagnostic-env.js +10 -1
  58. package/dist/runtime/engine-mode.d.ts +74 -0
  59. package/dist/runtime/engine-mode.js +152 -0
  60. package/dist/runtime/http-telemetry-guard.d.ts +10 -0
  61. package/dist/runtime/http-telemetry-guard.js +62 -10
  62. package/dist/runtime/http.js +141 -35
  63. package/dist/runtime/instrumentation.js +92 -13
  64. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  65. package/dist/runtime/ocr-telemetry.js +192 -0
  66. package/dist/runtime/ocr.js +30 -8
  67. package/dist/runtime/request-telemetry.d.ts +2 -0
  68. package/dist/runtime/request-telemetry.js +2 -1
  69. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  70. package/dist/runtime/resolver-runtime-options.js +33 -0
  71. package/dist/runtime/resolver-shared.d.ts +3 -2
  72. package/dist/runtime/resolver-shared.js +4 -4
  73. package/dist/runtime/resolver-telemetry.d.ts +1 -0
  74. package/dist/runtime/resolver-telemetry.js +1 -1
  75. package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
  76. package/dist/runtime/resolver-vendors/browser.js +46 -0
  77. package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
  78. package/dist/runtime/resolver-vendors/hosts.js +35 -0
  79. package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
  80. package/dist/runtime/resolver-vendors/types.d.ts +5 -4
  81. package/dist/runtime/resolver.d.ts +4 -1
  82. package/dist/runtime/resolver.js +46 -13
  83. package/dist/runtime/state-telemetry.d.ts +79 -0
  84. package/dist/runtime/state-telemetry.js +194 -0
  85. package/dist/runtime/state.d.ts +3 -0
  86. package/dist/runtime/state.js +26 -7
  87. package/dist/runtime/stealth-cookies.d.ts +5 -1
  88. package/dist/runtime/stealth-cookies.js +21 -1
  89. package/dist/runtime/stealth-owned-headers.d.ts +26 -0
  90. package/dist/runtime/stealth-owned-headers.js +37 -0
  91. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  92. package/dist/runtime/stealth-telemetry.js +179 -0
  93. package/dist/runtime/stealth.d.ts +4 -1
  94. package/dist/runtime/stealth.js +228 -85
  95. package/dist/runtime/stt-telemetry.d.ts +77 -0
  96. package/dist/runtime/stt-telemetry.js +176 -0
  97. package/dist/runtime/stt.js +15 -6
  98. package/dist/server/error-stack-frames.d.ts +35 -0
  99. package/dist/server/error-stack-frames.js +219 -0
  100. package/dist/server/index.d.ts +7 -0
  101. package/dist/server/index.js +4 -0
  102. package/dist/server/sdk-version.d.ts +3 -0
  103. package/dist/server/sdk-version.js +19 -0
  104. package/dist/server/self-test.js +1 -18
  105. package/dist/server/sensitive-values.d.ts +1 -1
  106. package/dist/server/sensitive-values.js +9 -0
  107. package/dist/server/serve-implementation.d.ts +51 -2
  108. package/dist/server/serve-implementation.js +337 -90
  109. package/dist/server/types.d.ts +1 -0
  110. package/dist/server/types.js +7 -0
  111. package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
  112. package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
  113. package/dist/stateful/stateful-provider-runtime-context.js +1 -0
  114. package/dist/types.d.ts +94 -1
  115. package/package.json +6 -1
  116. package/src/cli/commands.ts +14 -0
  117. package/src/cli/deployment-intent.ts +364 -0
  118. package/src/cli/migrate-deployment-intent.ts +1908 -0
  119. package/src/cli/migrate-provider-shape.ts +10 -4
  120. package/src/cli/resolver-runtime.ts +72 -0
  121. package/src/define.ts +13 -0
  122. package/src/dev.ts +9 -2
  123. package/src/engine.ts +38 -2
  124. package/src/error-resolution.ts +58 -0
  125. package/src/errors.ts +77 -1
  126. package/src/i18n/error-messages.ts +255 -0
  127. package/src/i18n/index.ts +1 -0
  128. package/src/i18n/keys.ts +80 -6
  129. package/src/index.ts +72 -0
  130. package/src/lint.ts +522 -50
  131. package/src/provider.ts +10 -0
  132. package/src/runtime/browser-telemetry-binding.ts +92 -0
  133. package/src/runtime/browser-telemetry.ts +289 -0
  134. package/src/runtime/browser.ts +249 -65
  135. package/src/runtime/cache-telemetry.ts +247 -0
  136. package/src/runtime/cache.ts +68 -17
  137. package/src/runtime/capability-response.ts +53 -0
  138. package/src/runtime/capability-telemetry.ts +101 -0
  139. package/src/runtime/diagnostic-env.ts +11 -1
  140. package/src/runtime/engine-mode.ts +233 -0
  141. package/src/runtime/http-telemetry-guard.ts +66 -13
  142. package/src/runtime/http.ts +168 -37
  143. package/src/runtime/instrumentation.ts +105 -16
  144. package/src/runtime/ocr-telemetry.ts +308 -0
  145. package/src/runtime/ocr.ts +109 -74
  146. package/src/runtime/request-telemetry.ts +2 -1
  147. package/src/runtime/resolver-runtime-options.ts +50 -0
  148. package/src/runtime/resolver-shared.ts +9 -4
  149. package/src/runtime/resolver-telemetry.ts +1 -1
  150. package/src/runtime/resolver-vendors/browser.ts +65 -7
  151. package/src/runtime/resolver-vendors/hosts.ts +43 -0
  152. package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
  153. package/src/runtime/resolver-vendors/types.ts +5 -4
  154. package/src/runtime/resolver.ts +64 -21
  155. package/src/runtime/state-telemetry.ts +298 -0
  156. package/src/runtime/state.ts +46 -45
  157. package/src/runtime/stealth-cookies.ts +24 -1
  158. package/src/runtime/stealth-owned-headers.ts +41 -0
  159. package/src/runtime/stealth-telemetry.ts +304 -0
  160. package/src/runtime/stealth.ts +355 -150
  161. package/src/runtime/stt-telemetry.ts +282 -0
  162. package/src/runtime/stt.ts +93 -71
  163. package/src/server/error-stack-frames.ts +214 -0
  164. package/src/server/index.ts +53 -0
  165. package/src/server/sdk-version.ts +22 -0
  166. package/src/server/self-test.ts +1 -21
  167. package/src/server/sensitive-values.ts +14 -0
  168. package/src/server/serve-implementation.ts +569 -108
  169. package/src/server/types.ts +7 -0
  170. package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
  171. package/src/stateful/stateful-provider-runtime-context.ts +4 -0
  172. package/src/types.ts +99 -1
  173. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
  174. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
  175. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
  176. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
  177. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
  178. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
  179. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
  180. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
  181. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
  182. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
  183. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
  184. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
  185. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
  186. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
  187. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
  188. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
  189. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
  190. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
  191. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
  192. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
  193. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
  194. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
  195. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
  196. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
  197. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
  198. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
  199. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
  200. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
  201. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
  202. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
  203. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
  204. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
  205. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
  206. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
  207. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
  208. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
  209. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
  210. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
  211. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
  212. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
  213. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
  214. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
  215. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
  216. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
  217. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
  218. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
  219. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
  220. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
  221. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
  222. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
  223. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
  224. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
  225. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
  226. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
  227. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
@@ -7,7 +7,9 @@ import {
7
7
  import { HttpRedirectError, ProviderError, TransportError } from "../errors.js";
8
8
  import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream.js";
9
9
  import type {
10
+ HttpAttemptContext,
10
11
  HttpClient,
12
+ HttpHeadersFactory,
11
13
  HttpMethod,
12
14
  HttpRedirectPolicy,
13
15
  HttpResponse,
@@ -194,26 +196,108 @@ function hasHeader(headers: Record<string, string>, name: string): boolean {
194
196
  return Object.keys(headers).some((key) => key.toLowerCase() === needle);
195
197
  }
196
198
 
197
- function withClientHeaders(
198
- options: RequestOptions | undefined,
199
+ function withClientHeaderDefaults(
200
+ headers: Record<string, string> | undefined,
199
201
  clientOptions: HttpClientOptions,
200
202
  body: unknown,
201
- ): RequestOptions {
202
- const headers: Record<string, string> = {
203
+ ): Record<string, string> {
204
+ const merged: Record<string, string> = {
203
205
  ...(clientOptions.userAgent ? { "User-Agent": clientOptions.userAgent } : {}),
204
- ...options?.headers,
206
+ ...headers,
205
207
  };
206
208
 
207
- if (body !== undefined && !hasHeader(headers, "Content-Type")) {
208
- headers["Content-Type"] = "application/json";
209
+ if (body !== undefined && !hasHeader(merged, "Content-Type")) {
210
+ merged["Content-Type"] = "application/json";
209
211
  }
210
212
 
213
+ return merged;
214
+ }
215
+
216
+ function withClientHeaders(
217
+ options: RequestOptions | undefined,
218
+ clientOptions: HttpClientOptions,
219
+ body: unknown,
220
+ ): RequestOptions {
221
+ // The ambient client signal is merged into the per-request one here so every
222
+ // downstream read (retry loop, sleep, body consumption) sees a single signal.
223
+ // The header-factory branch needs it too, otherwise factory-header requests
224
+ // would lose ambient cancellation entirely.
225
+ const signal = mergeAbortSignals(clientOptions.signal, options?.signal);
226
+ // A header factory is resolved per issued attempt (resolveAttemptHeaders);
227
+ // the client defaults are applied to its output at that point.
228
+ if (typeof options?.headers === "function") return { ...options, signal };
211
229
  return {
212
230
  ...options,
213
- headers,
231
+ headers: withClientHeaderDefaults(options?.headers, clientOptions, body),
232
+ signal,
214
233
  };
215
234
  }
216
235
 
236
+ function headerFactoryFailure(message: string, cause?: unknown): TransportError {
237
+ // A dedicated code keeps a broken proof minter out of the retry predicate:
238
+ // toHttpTransportError would otherwise brand a plain throw as
239
+ // transport_network_error, which the default retry policy re-issues.
240
+ // retryable:false is explicit so the served error envelope does not
241
+ // advertise a deterministic signing failure as retryable, and
242
+ // category:provider_error keeps a provider-local minting bug out of the
243
+ // upstream-failure signal (status 0 would otherwise fall through the
244
+ // observability ladder to upstream_http / source upstream_failure).
245
+ return new TransportError(message, {
246
+ code: "http_header_factory_failed",
247
+ status: 0,
248
+ retryable: false,
249
+ category: "provider_error",
250
+ ...(cause instanceof Error ? { cause } : {}),
251
+ });
252
+ }
253
+
254
+ function isHeaderRecord(value: unknown): value is Record<string, string> {
255
+ return value !== null && typeof value === "object" && !Array.isArray(value);
256
+ }
257
+
258
+ // Settle `promise`, or reject with the signal's reason as soon as it aborts, so
259
+ // a pending header factory cannot outlive the request deadline or an ambient
260
+ // cancellation (native fetch has not started yet, so nothing else observes them).
261
+ function settleBeforeAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {
262
+ if (!signal) return promise;
263
+ return new Promise<T>((resolve, reject) => {
264
+ const onAbort = () => reject(signal.reason);
265
+ if (signal.aborted) onAbort();
266
+ else signal.addEventListener("abort", onAbort, { once: true });
267
+ // Always attach handlers: a late rejection after the abort must settle
268
+ // here (a no-op on the already-rejected promise), never go unhandled.
269
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
270
+ });
271
+ }
272
+
273
+ async function resolveAttemptHeaders(
274
+ options: RequestOptions & { body?: unknown },
275
+ clientOptions: HttpClientOptions,
276
+ attempt: () => HttpAttemptContext,
277
+ signal: AbortSignal | undefined,
278
+ ): Promise<Record<string, string> | undefined> {
279
+ const { headers } = options;
280
+ if (typeof headers !== "function") return headers;
281
+ // The per-call timeout may have fired during proxy resolution: do not start
282
+ // the factory at all once the request is already aborted.
283
+ if (signal?.aborted) throw signal.reason;
284
+ let minted: unknown;
285
+ try {
286
+ minted = await settleBeforeAbort(
287
+ Promise.resolve().then(() => headers(attempt())),
288
+ signal,
289
+ );
290
+ } catch (error) {
291
+ // An abort is classified by toHttpTransportError (cancelled / timed out).
292
+ if (signal?.aborted) throw error;
293
+ throw headerFactoryFailure("Request header factory failed", error);
294
+ }
295
+ if (!isHeaderRecord(minted)) {
296
+ throw headerFactoryFailure("Request header factory must return a header record");
297
+ }
298
+ return withClientHeaderDefaults(minted, clientOptions, options.body);
299
+ }
300
+
217
301
  function parseHttpData(body: string, headers: Record<string, string>): unknown {
218
302
  const contentType = headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
219
303
 
@@ -469,7 +553,7 @@ function toNativeHttpStreamResponse(
469
553
  };
470
554
  }
471
555
 
472
- function normalizeHttpMethod(method: string): HttpMethod {
556
+ function normalizeHttpMethod(method: string): Uppercase<HttpMethod> {
473
557
  switch (method.toUpperCase()) {
474
558
  case "HEAD":
475
559
  return "HEAD";
@@ -765,7 +849,7 @@ function serializeHttpRequestUrl(
765
849
  async function fetchNativeHttp(
766
850
  baseUrl: string | undefined,
767
851
  url: string,
768
- method: HttpMethod,
852
+ method: Uppercase<HttpMethod>,
769
853
  options: RequestOptions & { body?: unknown },
770
854
  clientOptions: HttpClientOptions,
771
855
  warn: (message: string) => void,
@@ -773,24 +857,30 @@ async function fetchNativeHttp(
773
857
  proxyAttemptOffset = 0,
774
858
  dedupe?: { attempted: Set<string> },
775
859
  observeProxy?: (used: boolean, status?: number) => void,
860
+ nextHeaderAttempt: () => number = () => 1,
776
861
  ): Promise<NativeHttpAttemptOutcome> {
777
862
  const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
778
863
  const { requestUrl } = serializedUrl;
779
864
  const controller = options.timeout ? new AbortController() : undefined;
780
- const signal = mergeAbortSignals(clientOptions.signal, controller?.signal);
781
- const timeoutHandle = options.timeout
865
+ const signal = mergeAbortSignals(options.signal, controller?.signal);
866
+ let timeoutHandle = options.timeout
782
867
  ? setTimeout(() => controller?.abort(), options.timeout)
783
868
  : undefined;
869
+ const disarmTimeout = () => {
870
+ if (!timeoutHandle) return;
871
+ clearTimeout(timeoutHandle);
872
+ timeoutHandle = undefined;
873
+ };
784
874
 
785
875
  let proxy: string | undefined;
786
876
  try {
787
- throwIfAmbientAborted(clientOptions.signal);
877
+ throwIfAmbientAborted(options.signal);
788
878
  // Resolve inside the try (and after the timeout is armed) so allocator
789
879
  // failures are branded as TransportErrors and count against the request
790
880
  // deadline, exactly as an inline resolve would.
791
881
  proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
792
882
  observeProxy?.(Boolean(proxy));
793
- throwIfAmbientAborted(clientOptions.signal);
883
+ throwIfAmbientAborted(options.signal);
794
884
  // For a registry allocator chain, skip an endpoint a prior attempt already
795
885
  // tried rather than re-issuing the same request. Returning the sentinel
796
886
  // (instead of breaking) lets the loop keep advancing the flat offset until
@@ -801,8 +891,17 @@ async function fetchNativeHttp(
801
891
  }
802
892
  dedupe.attempted.add(proxy);
803
893
  }
894
+ // Resolve after the dedupe check so a skipped duplicate offset never burns
895
+ // a single-use proof, and with the serialized URL so URL-bound proofs
896
+ // match what is actually issued.
897
+ const requestHeaders = await resolveAttemptHeaders(
898
+ options,
899
+ clientOptions,
900
+ () => ({ attempt: nextHeaderAttempt(), url: requestUrl, method }),
901
+ signal,
902
+ );
804
903
  const requestInit: NativeFetchInit = {
805
- headers: options.headers,
904
+ headers: requestHeaders,
806
905
  method,
807
906
  ...(proxy ? { proxy } : {}),
808
907
  ...(signal ? { signal } : {}),
@@ -837,7 +936,15 @@ async function fetchNativeHttp(
837
936
  });
838
937
  }
839
938
 
840
- return toNativeHttpResponse(response);
939
+ // `timeout` bounds the header phase (types.ts RequestOptions.timeout), so it
940
+ // is disarmed before the body is read: awaiting the buffered body inside
941
+ // this try brands a mid-body failure as a transport error, but must not
942
+ // extend the deadline over the download. Cancellation still applies —
943
+ // `signal` stays attached to the native response. The discarded non-2xx
944
+ // bodies drained above stay under the deadline on purpose: nothing else
945
+ // bounds them, and their content is never returned to the caller.
946
+ disarmTimeout();
947
+ return await toNativeHttpResponse(response);
841
948
  } catch (error) {
842
949
  if (error instanceof SyntaxError) {
843
950
  throw redactSensitiveError(
@@ -848,7 +955,7 @@ async function fetchNativeHttp(
848
955
  );
849
956
  }
850
957
  const transportError: NativeHttpAttemptError = redactSensitiveError(
851
- toHttpTransportError(error, clientOptions.signal, controller?.signal),
958
+ toHttpTransportError(error, options.signal, controller?.signal),
852
959
  serializedUrl.sensitiveValues,
853
960
  serializedUrl.requestUrl,
854
961
  serializedUrl.redactedUrl,
@@ -856,14 +963,14 @@ async function fetchNativeHttp(
856
963
  transportError.proxyUsed = Boolean(proxy);
857
964
  throw transportError;
858
965
  } finally {
859
- if (timeoutHandle) clearTimeout(timeoutHandle);
966
+ disarmTimeout();
860
967
  }
861
968
  }
862
969
 
863
970
  async function fetchNativeHttpStream(
864
971
  baseUrl: string | undefined,
865
972
  url: string,
866
- method: HttpMethod,
973
+ method: Uppercase<HttpMethod>,
867
974
  options: RequestOptions & { body?: unknown },
868
975
  clientOptions: HttpClientOptions,
869
976
  warn: (message: string) => void,
@@ -872,18 +979,25 @@ async function fetchNativeHttpStream(
872
979
  const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
873
980
  const { requestUrl } = serializedUrl;
874
981
  const controller = options.timeout ? new AbortController() : undefined;
875
- const signal = mergeAbortSignals(clientOptions.signal, controller?.signal);
982
+ const signal = mergeAbortSignals(options.signal, controller?.signal);
876
983
  const timeoutHandle = options.timeout
877
984
  ? setTimeout(() => controller?.abort(), options.timeout)
878
985
  : undefined;
879
986
 
880
987
  try {
881
- throwIfAmbientAborted(clientOptions.signal);
988
+ throwIfAmbientAborted(options.signal);
882
989
  const proxy = await resolveNativeProxy(options, clientOptions, warn);
883
990
  observeProxy?.(Boolean(proxy));
884
- throwIfAmbientAborted(clientOptions.signal);
991
+ throwIfAmbientAborted(options.signal);
992
+ // Streams never retry: a header factory is resolved once, as attempt 1.
993
+ const headers = await resolveAttemptHeaders(
994
+ options,
995
+ clientOptions,
996
+ () => ({ attempt: 1, url: requestUrl, method }),
997
+ signal,
998
+ );
885
999
  const requestInit: NativeFetchInit = {
886
- headers: options.headers,
1000
+ headers,
887
1001
  method,
888
1002
  ...(proxy ? { proxy } : {}),
889
1003
  ...(signal ? { signal } : {}),
@@ -905,9 +1019,9 @@ async function fetchNativeHttpStream(
905
1019
  });
906
1020
  }
907
1021
 
908
- // Per-call timeout remains header-scoped, while the ambient request signal
909
- // stays attached to the response body for its full consumption lifetime.
910
- return toNativeHttpStreamResponse(response, serializedUrl, clientOptions.signal);
1022
+ // The stream timeout remains header-scoped; composed ambient and local
1023
+ // cancellation stays attached to native fetch for the body's full lifetime.
1024
+ return toNativeHttpStreamResponse(response, serializedUrl, options.signal);
911
1025
  } catch (error) {
912
1026
  if (error instanceof SyntaxError) {
913
1027
  throw redactSensitiveError(
@@ -918,7 +1032,7 @@ async function fetchNativeHttpStream(
918
1032
  );
919
1033
  }
920
1034
  throw redactSensitiveError(
921
- toHttpTransportError(error, clientOptions.signal, controller?.signal),
1035
+ toHttpTransportError(error, options.signal, controller?.signal),
922
1036
  serializedUrl.sensitiveValues,
923
1037
  serializedUrl.requestUrl,
924
1038
  serializedUrl.redactedUrl,
@@ -1052,6 +1166,16 @@ export function createHttpClient(
1052
1166
  });
1053
1167
  const dedupeContext = dedupeAllocatorEndpoints ? { attempted: new Set<string>() } : undefined;
1054
1168
 
1169
+ // Numbers the attempts a header factory sees: only requests that reached
1170
+ // header resolution count, so a failed proxy allocation (which increments
1171
+ // the telemetry `issued` count below) or a dedupe-skipped offset does not
1172
+ // make the first request that is actually built report `attempt: 2`.
1173
+ let headerAttempts = 0;
1174
+ const nextHeaderAttempt = () => {
1175
+ headerAttempts += 1;
1176
+ return headerAttempts;
1177
+ };
1178
+
1055
1179
  const executeOnce = async (proxyAttemptOffset = 0): Promise<NativeHttpAttemptOutcome> => {
1056
1180
  const started = performance.now();
1057
1181
  let proxyUsed = false;
@@ -1074,6 +1198,7 @@ export function createHttpClient(
1074
1198
  proxyUsed = used;
1075
1199
  observedStatus = status;
1076
1200
  },
1201
+ nextHeaderAttempt,
1077
1202
  );
1078
1203
  if (!isDedupeSkipOutcome(outcome))
1079
1204
  record({
@@ -1091,7 +1216,7 @@ export function createHttpClient(
1091
1216
  };
1092
1217
 
1093
1218
  if (!retryEnabled || !retryOptions) {
1094
- throwIfAmbientAborted(clientOptions.signal);
1219
+ throwIfAmbientAborted(headersOptions.signal);
1095
1220
  const outcome = await executeOnce();
1096
1221
  if (isDedupeSkipOutcome(outcome)) {
1097
1222
  // Single-shot path never de-duplicates (dedupeContext is undefined),
@@ -1114,7 +1239,7 @@ export function createHttpClient(
1114
1239
  // allocations skip offsets.
1115
1240
  let issued = 0;
1116
1241
  for (let attempt = 1; attempt <= transportAttemptCap; attempt += 1) {
1117
- throwIfAmbientAborted(clientOptions.signal);
1242
+ throwIfAmbientAborted(headersOptions.signal);
1118
1243
  // Whether this offset actually issued a request (vs. a skipped duplicate),
1119
1244
  // so the catch counts a thrown *transport* failure once without
1120
1245
  // double-counting a status outcome that already incremented before it
@@ -1134,7 +1259,7 @@ export function createHttpClient(
1134
1259
  if (outcome.retryable && issued < retryOptions.attempts) {
1135
1260
  await sleep(
1136
1261
  computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers),
1137
- clientOptions.signal,
1262
+ headersOptions.signal,
1138
1263
  );
1139
1264
  continue;
1140
1265
  }
@@ -1152,7 +1277,7 @@ export function createHttpClient(
1152
1277
  }
1153
1278
  return response;
1154
1279
  } catch (error) {
1155
- throwIfAmbientAborted(clientOptions.signal);
1280
+ throwIfAmbientAborted(headersOptions.signal);
1156
1281
  if (!issuedThisAttempt) issued += 1;
1157
1282
  lastError = error;
1158
1283
  const proxyUsed = Boolean((error as NativeHttpAttemptError).proxyUsed);
@@ -1168,7 +1293,7 @@ export function createHttpClient(
1168
1293
  ) {
1169
1294
  await sleep(
1170
1295
  computeProxyTransportRetryDelayMs(retryOptions, attempt),
1171
- clientOptions.signal,
1296
+ headersOptions.signal,
1172
1297
  );
1173
1298
  continue;
1174
1299
  }
@@ -1218,7 +1343,7 @@ export function createHttpClient(
1218
1343
  // Streams never retry. Observe the header fetch without consuming its body.
1219
1344
  const telemetry = startHttpTelemetry(scopedHttpTelemetry(clientOptions.httpTelemetry), "off");
1220
1345
  const started = performance.now();
1221
- const alreadyAborted = clientOptions.signal?.aborted === true;
1346
+ const alreadyAborted = headersOptions.signal?.aborted === true;
1222
1347
  let proxyUsed = false;
1223
1348
  const record = (event: Omit<HttpAttemptTelemetryEvent, "ms" | "proxyUsed">) => {
1224
1349
  if (alreadyAborted) return;
@@ -1259,10 +1384,16 @@ export function createHttpClient(
1259
1384
  url,
1260
1385
  options: RequestWithMethodOptions = {},
1261
1386
  ): Promise<AsyncIterable<SseMessage>> => {
1262
- const headers = {
1263
- Accept: "text/event-stream",
1264
- ...options.headers,
1265
- };
1387
+ const callerHeaders = options.headers;
1388
+ const headers: Record<string, string> | HttpHeadersFactory =
1389
+ typeof callerHeaders === "function"
1390
+ ? async (attempt) => {
1391
+ // Pass a malformed result through unchanged so the factory
1392
+ // validation rejects it instead of spreading it into `{}`.
1393
+ const minted = await callerHeaders(attempt);
1394
+ return isHeaderRecord(minted) ? { Accept: "text/event-stream", ...minted } : minted;
1395
+ }
1396
+ : { Accept: "text/event-stream", ...callerHeaders };
1266
1397
  const response = await streamRequest(url, options.method ?? "GET", {
1267
1398
  ...options,
1268
1399
  headers,
@@ -1,3 +1,4 @@
1
+ import { runCapabilitySpan } from "./capability-telemetry.js";
1
2
  import { registerDiagnosticSensitiveValues } from "./diagnostic-redactor.js";
2
3
  import type {
3
4
  HttpStreamResponse,
@@ -35,9 +36,26 @@ export type InstrumentedProviderContext<T extends Pick<ProviderContext, "trace">
35
36
  trace: TraceContext;
36
37
  };
37
38
 
38
- type InstrumentedNamespace = "http" | "stealth" | "browser" | "session" | "state" | "resolver";
39
-
40
- const BROWSER_PAGE_METHODS = new Set(["goto", "fill", "click", "type", "waitForSelector"]);
39
+ type InstrumentedNamespace =
40
+ | "http"
41
+ | "stealth"
42
+ | "browser"
43
+ | "session"
44
+ | "state"
45
+ | "resolver"
46
+ | "ocr"
47
+ | "stt";
48
+
49
+ const BROWSER_PAGE_METHODS = new Set([
50
+ "goto",
51
+ "fill",
52
+ "click",
53
+ "type",
54
+ "waitForSelector",
55
+ "evaluate",
56
+ "content",
57
+ "screenshot",
58
+ ]);
41
59
  const DIAGNOSTIC_BASE_URL = "http://apifuse-instrumentation.invalid";
42
60
 
43
61
  type RequestDiagnostics = {
@@ -398,9 +416,19 @@ function buildSpanAttributes(
398
416
  attributes.method = method;
399
417
  }
400
418
 
401
- if (status !== undefined && (namespace === "http" || namespace === "stealth")) {
419
+ if (
420
+ status !== undefined &&
421
+ (namespace === "http" || namespace === "stealth" || namespace === "ocr" || namespace === "stt")
422
+ ) {
402
423
  attributes.status = status;
403
424
  }
425
+ if ((namespace === "ocr" || namespace === "stt") && result && typeof result === "object") {
426
+ if ("model" in result && typeof result.model === "string") attributes.model = result.model;
427
+ if ("durationMs" in result && typeof result.durationMs === "number")
428
+ attributes.duration_ms = result.durationMs;
429
+ if ("warnings" in result && Array.isArray(result.warnings))
430
+ attributes.warnings = result.warnings.length;
431
+ }
404
432
 
405
433
  if (duration !== undefined) {
406
434
  attributes.duration_ms = duration;
@@ -538,18 +566,33 @@ function wrapPage<T extends object>(page: T, trace: TraceContext): T {
538
566
  const methodName = String(property);
539
567
  const wrapped = (...args: unknown[]) => {
540
568
  let elapsedMs = 0;
569
+ const diagnostics = snapshotRequestDiagnostics("browser", methodName, args);
570
+ registerDiagnosticSensitiveValues(trace, diagnostics.sensitiveValues);
541
571
 
542
572
  return recorder.runSpan(
543
- `browser.page.${methodName}`,
573
+ ["evaluate", "content", "screenshot"].includes(methodName)
574
+ ? `browser.${methodName}`
575
+ : `browser.page.${methodName}`,
544
576
  async () => {
545
577
  const startedAt = Date.now();
546
- const result = await Reflect.apply(value, pageTarget, args);
578
+ let result: unknown;
579
+ try {
580
+ result = await Reflect.apply(value, pageTarget, args);
581
+ } catch (error) {
582
+ throw sanitizeRequestError(error, diagnostics);
583
+ }
547
584
  elapsedMs = Date.now() - startedAt;
548
585
  return result;
549
586
  },
550
587
  {
551
- onSuccess: () => getBrowserPageAttributes(methodName, args, elapsedMs),
552
- onError: (error) => getBrowserPageAttributes(methodName, args, undefined, error),
588
+ onSuccess: () => ({
589
+ ...getBrowserPageAttributes(methodName, args, elapsedMs),
590
+ ...(diagnostics.traceUrl ? { url: diagnostics.traceUrl } : {}),
591
+ }),
592
+ onError: (error) => ({
593
+ ...getBrowserPageAttributes(methodName, args, undefined, error),
594
+ ...(diagnostics.traceUrl ? { url: diagnostics.traceUrl } : {}),
595
+ }),
553
596
  },
554
597
  );
555
598
  };
@@ -621,10 +664,15 @@ function wrapNamespace<T extends object>(
621
664
  trace: TraceContext,
622
665
  shouldInstrument?: (methodName: string, args: unknown[]) => boolean,
623
666
  ): T {
624
- const recorder = getTraceRecorder(trace);
625
- if (!recorder) {
626
- return target;
627
- }
667
+ const baseRecorder = getTraceRecorder(trace);
668
+ if (!baseRecorder) return target;
669
+ const recorder: NonNullable<ReturnType<typeof getTraceRecorder>> =
670
+ namespace === "ocr" || namespace === "stt"
671
+ ? {
672
+ runSpan: (name, fn, options) =>
673
+ runCapabilitySpan(target, () => baseRecorder.runSpan(name, fn, options)),
674
+ }
675
+ : baseRecorder;
628
676
 
629
677
  const wrappedMethods = new Map<PropertyKey, unknown>();
630
678
  const resolverMetadata =
@@ -719,6 +767,32 @@ function wrapNamespace<T extends object>(
719
767
  return wrapped;
720
768
  }
721
769
 
770
+ if (namespace === "browser" && property === "rawPage") {
771
+ if (wrappedMethods.has(property)) return wrappedMethods.get(property);
772
+ const wrapped = (...args: unknown[]) =>
773
+ recorder.runSpan("browser.rawPage", async () => {
774
+ const page = await Reflect.apply(value, namespaceTarget, args);
775
+ return wrapPage(page, trace);
776
+ });
777
+ wrappedMethods.set(property, wrapped);
778
+ return wrapped;
779
+ }
780
+
781
+ if (namespace === "browser" && property === "withIsolatedContext") {
782
+ if (wrappedMethods.has(property)) return wrappedMethods.get(property);
783
+ const wrapped = (handler: unknown, ...args: unknown[]) =>
784
+ recorder.runSpan("browser.withIsolatedContext", async () => {
785
+ if (typeof handler !== "function")
786
+ return Reflect.apply(value, namespaceTarget, [handler, ...args]);
787
+ return Reflect.apply(value, namespaceTarget, [
788
+ (page: object) => Reflect.apply(handler, undefined, [wrapPage(page, trace)]),
789
+ ...args,
790
+ ]);
791
+ });
792
+ wrappedMethods.set(property, wrapped);
793
+ return wrapped;
794
+ }
795
+
722
796
  if (wrappedMethods.has(property)) {
723
797
  return wrappedMethods.get(property);
724
798
  }
@@ -727,18 +801,31 @@ function wrapNamespace<T extends object>(
727
801
  if (namespace === "browser") {
728
802
  const wrapped = (...args: unknown[]) => {
729
803
  let elapsedMs = 0;
804
+ const diagnostics = snapshotRequestDiagnostics("browser", methodName, args);
805
+ registerDiagnosticSensitiveValues(trace, diagnostics.sensitiveValues);
730
806
 
731
807
  return recorder.runSpan(
732
808
  `browser.${methodName}`,
733
809
  async () => {
734
810
  const startedAt = Date.now();
735
- const result = await Reflect.apply(value, namespaceTarget, args);
811
+ let result: unknown;
812
+ try {
813
+ result = await Reflect.apply(value, namespaceTarget, args);
814
+ } catch (error) {
815
+ throw sanitizeRequestError(error, diagnostics);
816
+ }
736
817
  elapsedMs = Date.now() - startedAt;
737
818
  return result;
738
819
  },
739
820
  {
740
- onSuccess: () => getBrowserPageAttributes(methodName, args, elapsedMs),
741
- onError: (error) => getBrowserPageAttributes(methodName, args, undefined, error),
821
+ onSuccess: () => ({
822
+ ...getBrowserPageAttributes(methodName, args, elapsedMs),
823
+ ...(diagnostics.traceUrl ? { url: diagnostics.traceUrl } : {}),
824
+ }),
825
+ onError: (error) => ({
826
+ ...getBrowserPageAttributes(methodName, args, undefined, error),
827
+ ...(diagnostics.traceUrl ? { url: diagnostics.traceUrl } : {}),
828
+ }),
742
829
  },
743
830
  );
744
831
  };
@@ -878,7 +965,9 @@ export function wrapWithInstrumentation<T extends Pick<ProviderContext, "trace">
878
965
  property === "browser" ||
879
966
  property === "session" ||
880
967
  property === "state" ||
881
- property === "resolver"
968
+ property === "resolver" ||
969
+ property === "ocr" ||
970
+ property === "stt"
882
971
  ) {
883
972
  const namespace = property;
884
973
  if (wrappedTargets.has(namespace)) {