@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,34 +1,56 @@
1
- import type { ProviderContext } from "../types";
1
+ import type {
2
+ HttpStreamResponse,
3
+ ProviderContext,
4
+ RequestOptions,
5
+ SseMessage,
6
+ StealthSession,
7
+ StealthRedirectRunResult,
8
+ } from "../types.js";
9
+ import { readableBytes, readableLines, readableTextChunks } from "../stream.js";
10
+ import {
11
+ parseHttpRequestInvocation,
12
+ isSensitiveKey,
13
+ redactSensitiveError,
14
+ redactSensitiveText,
15
+ redactUrlQueryParams,
16
+ requestOptionsFromHttpInvocation,
17
+ serializeRequestUrl,
18
+ type SerializedRequestUrl,
19
+ } from "./request-options.js";
2
20
  import {
3
21
  type CreateTraceContextOptions,
4
22
  createTraceContext,
5
23
  getTraceRecorder,
6
24
  type TraceContext,
7
- } from "./trace";
25
+ } from "./trace.js";
8
26
 
9
27
  export interface InstrumentationOptions extends CreateTraceContextOptions {}
10
28
 
11
- export type InstrumentedProviderContext<T extends ProviderContext> = Omit<
12
- T,
13
- "trace"
14
- > & {
29
+ export type InstrumentedProviderContext<T extends ProviderContext> = Omit<T, "trace"> & {
15
30
  trace: TraceContext;
16
31
  };
17
32
 
18
- type InstrumentedNamespace =
19
- | "http"
20
- | "stealth"
21
- | "browser"
22
- | "session"
23
- | "state";
24
-
25
- const BROWSER_PAGE_METHODS = new Set([
26
- "goto",
27
- "fill",
28
- "click",
29
- "type",
30
- "waitForSelector",
31
- ]);
33
+ type InstrumentedNamespace = "http" | "stealth" | "browser" | "session" | "state";
34
+
35
+ const BROWSER_PAGE_METHODS = new Set(["goto", "fill", "click", "type", "waitForSelector"]);
36
+ const DIAGNOSTIC_BASE_URL = "http://apifuse-instrumentation.invalid";
37
+
38
+ type RequestDiagnostics = {
39
+ diagnosticUrlDegraded?: boolean;
40
+ requestId?: string;
41
+ serializedUrl?: SerializedRequestUrl;
42
+ sensitiveParamNames: readonly string[];
43
+ sensitiveValues: readonly string[];
44
+ traceUrl?: string;
45
+ };
46
+
47
+ function isThenable(value: unknown): value is PromiseLike<unknown> {
48
+ return (
49
+ (typeof value === "object" || typeof value === "function") &&
50
+ value !== null &&
51
+ typeof (value as { then?: unknown }).then === "function"
52
+ );
53
+ }
32
54
 
33
55
  function getErrorStatus(error: unknown): number | undefined {
34
56
  if (
@@ -59,10 +81,7 @@ function getResponseDuration(result: unknown): number | undefined {
59
81
  return undefined;
60
82
  }
61
83
 
62
- function getResponseStatus(
63
- namespace: InstrumentedNamespace,
64
- result: unknown,
65
- ): number | undefined {
84
+ function getResponseStatus(namespace: InstrumentedNamespace, result: unknown): number | undefined {
66
85
  if (
67
86
  typeof result === "object" &&
68
87
  result !== null &&
@@ -101,18 +120,236 @@ function getUrl(
101
120
  return undefined;
102
121
  }
103
122
 
123
+ function requestOptionsForInvocation(
124
+ namespace: InstrumentedNamespace,
125
+ methodName: string,
126
+ args: readonly unknown[],
127
+ ): RequestOptions | undefined {
128
+ if (namespace === "http") {
129
+ const invocation = parseHttpRequestInvocation(methodName, [...args]);
130
+ return invocation ? requestOptionsFromHttpInvocation(invocation) : undefined;
131
+ }
132
+ if (namespace !== "stealth" || methodName !== "fetch" || typeof args[0] !== "string") {
133
+ return undefined;
134
+ }
135
+ const options = args[1];
136
+ return options !== null && typeof options === "object" && !Array.isArray(options)
137
+ ? (options as RequestOptions)
138
+ : undefined;
139
+ }
140
+
141
+ function fallbackSensitiveValues(options?: RequestOptions): readonly string[] {
142
+ const sensitiveParams = options?.sensitiveParams;
143
+ if (!sensitiveParams || typeof sensitiveParams !== "object") return [];
144
+ return Object.values(sensitiveParams).map(String);
145
+ }
146
+
147
+ function stripDiagnosticBase(url: string): string {
148
+ return url.startsWith(DIAGNOSTIC_BASE_URL) ? url.slice(DIAGNOSTIC_BASE_URL.length) || "/" : url;
149
+ }
150
+
151
+ function serializeDiagnosticUrl(
152
+ url: string,
153
+ options?: RequestOptions,
154
+ ): { degraded: boolean; serializedUrl: SerializedRequestUrl } {
155
+ try {
156
+ return {
157
+ degraded: false,
158
+ serializedUrl: serializeRequestUrl(url, options?.params, options?.sensitiveParams),
159
+ };
160
+ } catch {
161
+ try {
162
+ const absoluteUrl = new URL(url, DIAGNOSTIC_BASE_URL).toString();
163
+ const serialized = serializeRequestUrl(
164
+ absoluteUrl,
165
+ options?.params,
166
+ options?.sensitiveParams,
167
+ );
168
+ return {
169
+ degraded: false,
170
+ serializedUrl: {
171
+ requestUrl: stripDiagnosticBase(serialized.requestUrl),
172
+ redactedUrl: stripDiagnosticBase(serialized.redactedUrl),
173
+ sensitiveValues: serialized.sensitiveValues,
174
+ },
175
+ };
176
+ } catch {
177
+ const sensitiveParamNames = Object.keys(options?.sensitiveParams ?? {});
178
+ const structural = redactUrlQueryParams(url, sensitiveParamNames);
179
+ return {
180
+ degraded: true,
181
+ serializedUrl: {
182
+ requestUrl: url,
183
+ redactedUrl: structural.redactedUrl,
184
+ sensitiveValues: [
185
+ ...new Set([...fallbackSensitiveValues(options), ...structural.sensitiveValues]),
186
+ ],
187
+ },
188
+ };
189
+ }
190
+ }
191
+ }
192
+
193
+ function snapshotRequestDiagnostics(
194
+ namespace: InstrumentedNamespace,
195
+ methodName: string,
196
+ args: readonly unknown[],
197
+ ): RequestDiagnostics {
198
+ const options = requestOptionsForInvocation(namespace, methodName, args);
199
+ const url = typeof args[0] === "string" ? args[0] : undefined;
200
+ const hasSensitiveParams = Boolean(
201
+ options?.sensitiveParams && Object.keys(options.sensitiveParams).length > 0,
202
+ );
203
+ const sensitiveParamNames = Object.keys(options?.sensitiveParams ?? {});
204
+ const diagnosticUrl =
205
+ hasSensitiveParams && url ? serializeDiagnosticUrl(url, options) : undefined;
206
+ const traceUrl =
207
+ hasSensitiveParams && url
208
+ ? serializeDiagnosticUrl(url, { sensitiveParams: options?.sensitiveParams }).serializedUrl
209
+ .redactedUrl
210
+ : undefined;
211
+ return {
212
+ ...(diagnosticUrl?.degraded ? { diagnosticUrlDegraded: true } : {}),
213
+ ...(hasSensitiveParams ? { requestId: crypto.randomUUID() } : {}),
214
+ serializedUrl: diagnosticUrl?.serializedUrl,
215
+ sensitiveParamNames,
216
+ sensitiveValues:
217
+ diagnosticUrl?.serializedUrl.sensitiveValues ?? fallbackSensitiveValues(options),
218
+ traceUrl,
219
+ };
220
+ }
221
+
222
+ function sanitizeRequestError(error: unknown, diagnostics: RequestDiagnostics): unknown {
223
+ return redactSensitiveError(
224
+ error,
225
+ diagnostics.sensitiveValues,
226
+ diagnostics.serializedUrl?.requestUrl,
227
+ diagnostics.serializedUrl?.redactedUrl,
228
+ );
229
+ }
230
+
231
+ function isHttpStreamResponse(value: unknown): value is HttpStreamResponse {
232
+ return (
233
+ typeof value === "object" &&
234
+ value !== null &&
235
+ "body" in value &&
236
+ value.body instanceof ReadableStream
237
+ );
238
+ }
239
+
240
+ function instrumentHttpStreamConsumption(
241
+ value: HttpStreamResponse,
242
+ recorder: NonNullable<ReturnType<typeof getTraceRecorder>>,
243
+ args: unknown[],
244
+ diagnostics: RequestDiagnostics,
245
+ ): HttpStreamResponse {
246
+ const source = value.body;
247
+ let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
248
+ const body = new ReadableStream<Uint8Array>(
249
+ {
250
+ async pull(controller) {
251
+ try {
252
+ reader ??= source.getReader();
253
+ const chunk = await reader.read();
254
+ if (chunk.done) {
255
+ controller.close();
256
+ return;
257
+ }
258
+ controller.enqueue(chunk.value);
259
+ } catch (error) {
260
+ const sanitizedError = sanitizeRequestError(error, diagnostics);
261
+ try {
262
+ await recorder.runSpan(
263
+ "http.stream.consume",
264
+ () => {
265
+ throw sanitizedError;
266
+ },
267
+ {
268
+ onError: (spanError) =>
269
+ buildSpanAttributes("http", "stream", args, undefined, spanError, diagnostics),
270
+ },
271
+ );
272
+ } catch (recordedError) {
273
+ controller.error(recordedError);
274
+ }
275
+ }
276
+ },
277
+ async cancel(reason) {
278
+ try {
279
+ await (reader ? reader.cancel(reason) : source.cancel(reason));
280
+ } catch (error) {
281
+ throw sanitizeRequestError(error, diagnostics);
282
+ }
283
+ },
284
+ },
285
+ { highWaterMark: 0 },
286
+ );
287
+
288
+ const instrumented = {
289
+ ...value,
290
+ body,
291
+ bytes: () => readableBytes(body),
292
+ textChunks: () => readableTextChunks(body),
293
+ lines: () => readableLines(body),
294
+ } satisfies HttpStreamResponse;
295
+ try {
296
+ Object.assign(value, instrumented);
297
+ return value;
298
+ } catch {
299
+ return instrumented;
300
+ }
301
+ }
302
+
303
+ function isAsyncIterable<T = unknown>(value: unknown): value is AsyncIterable<T> {
304
+ return (
305
+ typeof value === "object" &&
306
+ value !== null &&
307
+ Symbol.asyncIterator in value &&
308
+ typeof value[Symbol.asyncIterator] === "function"
309
+ );
310
+ }
311
+
312
+ function instrumentHttpSseConsumption(
313
+ value: AsyncIterable<SseMessage>,
314
+ recorder: NonNullable<ReturnType<typeof getTraceRecorder>>,
315
+ args: unknown[],
316
+ diagnostics: RequestDiagnostics,
317
+ ): AsyncIterable<SseMessage> {
318
+ const source = value;
319
+ return {
320
+ async *[Symbol.asyncIterator]() {
321
+ try {
322
+ for await (const event of source) yield event;
323
+ } catch (error) {
324
+ const sanitizedError = sanitizeRequestError(error, diagnostics);
325
+ return await recorder.runSpan(
326
+ "http.sse.consume",
327
+ () => {
328
+ throw sanitizedError;
329
+ },
330
+ {
331
+ onError: (spanError) =>
332
+ buildSpanAttributes("http", "sse", args, undefined, spanError, diagnostics),
333
+ },
334
+ );
335
+ }
336
+ },
337
+ };
338
+ }
339
+
104
340
  function getMethod(
105
341
  namespace: InstrumentedNamespace,
106
342
  methodName: string,
107
343
  args: unknown[],
108
344
  ): string | undefined {
109
345
  if (namespace === "http") {
346
+ // Preserve the original instrumentation contract: generic entry points are
347
+ // reported as REQUEST/STREAM/SSE, independent of transport options.
110
348
  return methodName.toUpperCase();
111
349
  }
112
350
 
113
351
  if (namespace === "stealth") {
114
- const options =
115
- typeof args[1] === "object" && args[1] !== null ? args[1] : undefined;
352
+ const options = typeof args[1] === "object" && args[1] !== null ? args[1] : undefined;
116
353
  if (options && "method" in options && typeof options.method === "string") {
117
354
  return options.method.toUpperCase();
118
355
  }
@@ -129,27 +366,34 @@ function buildSpanAttributes(
129
366
  args: unknown[],
130
367
  result?: unknown,
131
368
  error?: unknown,
369
+ diagnostics: RequestDiagnostics = { sensitiveParamNames: [], sensitiveValues: [] },
132
370
  ): Record<string, string | number | boolean> {
133
371
  const attributes: Record<string, string | number | boolean> = {};
134
- const url = getUrl(namespace, args, result);
372
+ const rawUrl = getUrl(namespace, args, result);
373
+ const structuralUrl = diagnostics.traceUrl ?? rawUrl;
374
+ const url = structuralUrl
375
+ ? redactSensitiveText(
376
+ structuralUrl,
377
+ diagnostics.sensitiveValues,
378
+ diagnostics.serializedUrl?.requestUrl,
379
+ diagnostics.serializedUrl?.redactedUrl,
380
+ )
381
+ : undefined;
135
382
  const method = getMethod(namespace, methodName, args);
136
- const status = error
137
- ? getErrorStatus(error)
138
- : getResponseStatus(namespace, result);
383
+ const status = error ? getErrorStatus(error) : getResponseStatus(namespace, result);
139
384
  const duration = error ? undefined : getResponseDuration(result);
140
385
 
141
386
  if (url) {
142
387
  attributes.url = url;
143
388
  }
389
+ if (diagnostics.requestId) attributes.request_id = diagnostics.requestId;
390
+ if (diagnostics.diagnosticUrlDegraded) attributes.redaction_degraded = true;
144
391
 
145
392
  if (method) {
146
393
  attributes.method = method;
147
394
  }
148
395
 
149
- if (
150
- status !== undefined &&
151
- (namespace === "http" || namespace === "stealth")
152
- ) {
396
+ if (status !== undefined && (namespace === "http" || namespace === "stealth")) {
153
397
  attributes.status = status;
154
398
  }
155
399
 
@@ -168,6 +412,64 @@ function buildSpanAttributes(
168
412
  return attributes;
169
413
  }
170
414
 
415
+ function isStealthRedirectRunResult(value: unknown): value is StealthRedirectRunResult {
416
+ return (
417
+ typeof value === "object" &&
418
+ value !== null &&
419
+ "reason" in value &&
420
+ typeof value.reason === "string" &&
421
+ "hops" in value &&
422
+ Array.isArray(value.hops) &&
423
+ "final" in value &&
424
+ typeof value.final === "object" &&
425
+ value.final !== null
426
+ );
427
+ }
428
+
429
+ function buildStealthRedirectAttributes(
430
+ args: unknown[],
431
+ result: unknown,
432
+ error: unknown,
433
+ diagnostics: RequestDiagnostics,
434
+ ): Record<string, string | number | boolean> {
435
+ const attributes = buildSpanAttributes("stealth", "fetch", args, undefined, error, diagnostics);
436
+ if (!isStealthRedirectRunResult(result)) return attributes;
437
+
438
+ attributes.redirect_reason = result.reason;
439
+ attributes.redirect_hop_count = result.hops.length;
440
+ attributes.status = result.final.status;
441
+ if (result.hops.length > 0) {
442
+ const sensitiveValues = new Set(diagnostics.sensitiveValues);
443
+ const path = result.hops
444
+ .map((hop) => {
445
+ const hopUrl = hop.nextUrl ?? hop.url;
446
+ const responseSensitiveParamNames = [...queryParamNames(hopUrl)].filter(isSensitiveKey);
447
+ const structural = redactUrlQueryParams(hopUrl, [
448
+ ...new Set([...diagnostics.sensitiveParamNames, ...responseSensitiveParamNames]),
449
+ ]);
450
+ for (const value of structural.sensitiveValues) sensitiveValues.add(value);
451
+ const safeUrl = redactSensitiveText(structural.redactedUrl, [...sensitiveValues]);
452
+ return `${hop.method} ${hop.status} ${safeUrl}`;
453
+ })
454
+ .join(" -> ");
455
+ attributes.redirect_path = redactSensitiveText(
456
+ path,
457
+ [...sensitiveValues],
458
+ diagnostics.serializedUrl?.requestUrl,
459
+ diagnostics.serializedUrl?.redactedUrl,
460
+ );
461
+ }
462
+ return attributes;
463
+ }
464
+
465
+ function queryParamNames(url: string): Set<string> {
466
+ const queryStart = url.indexOf("?");
467
+ if (queryStart === -1) return new Set();
468
+ const fragmentStart = url.indexOf("#", queryStart);
469
+ const query = url.slice(queryStart + 1, fragmentStart === -1 ? undefined : fragmentStart);
470
+ return new Set(new URLSearchParams(query).keys());
471
+ }
472
+
171
473
  function getBrowserPageAttributes(
172
474
  methodName: string,
173
475
  args: unknown[],
@@ -241,10 +543,8 @@ function wrapPage<T extends object>(page: T, trace: TraceContext): T {
241
543
  return result;
242
544
  },
243
545
  {
244
- onSuccess: () =>
245
- getBrowserPageAttributes(methodName, args, elapsedMs),
246
- onError: (error) =>
247
- getBrowserPageAttributes(methodName, args, undefined, error),
546
+ onSuccess: () => getBrowserPageAttributes(methodName, args, elapsedMs),
547
+ onError: (error) => getBrowserPageAttributes(methodName, args, undefined, error),
248
548
  },
249
549
  );
250
550
  };
@@ -255,10 +555,65 @@ function wrapPage<T extends object>(page: T, trace: TraceContext): T {
255
555
  });
256
556
  }
257
557
 
558
+ function wrapStealthRedirects(
559
+ redirects: StealthSession["redirects"],
560
+ trace: TraceContext,
561
+ ): StealthSession["redirects"] {
562
+ const recorder = getTraceRecorder(trace);
563
+ if (!recorder) return redirects;
564
+
565
+ return {
566
+ run(...args: Parameters<StealthSession["redirects"]["run"]>) {
567
+ const diagnosticArgs = [args[0].url, args[0]];
568
+ const diagnostics = snapshotRequestDiagnostics("stealth", "fetch", diagnosticArgs);
569
+ let result: ReturnType<StealthSession["redirects"]["run"]>;
570
+ try {
571
+ result = redirects.run(...args);
572
+ } catch (error) {
573
+ const sanitizedError = sanitizeRequestError(error, diagnostics);
574
+ recorder
575
+ .runSpan(
576
+ "stealth.redirects.run",
577
+ () => {
578
+ throw sanitizedError;
579
+ },
580
+ {
581
+ onError: (spanError) =>
582
+ buildStealthRedirectAttributes(diagnosticArgs, undefined, spanError, diagnostics),
583
+ },
584
+ )
585
+ .catch(() => undefined);
586
+ throw sanitizedError;
587
+ }
588
+
589
+ const sanitizedResult = Promise.resolve(result).catch((error: unknown) => {
590
+ throw sanitizeRequestError(error, diagnostics);
591
+ });
592
+ return recorder.runSpan("stealth.redirects.run", () => sanitizedResult, {
593
+ onSuccess: (spanResult) =>
594
+ buildStealthRedirectAttributes(diagnosticArgs, spanResult, undefined, diagnostics),
595
+ onError: (error) =>
596
+ buildStealthRedirectAttributes(diagnosticArgs, undefined, error, diagnostics),
597
+ });
598
+ },
599
+ };
600
+ }
601
+
602
+ function wrapStealthSession(session: StealthSession, trace: TraceContext): StealthSession {
603
+ const wrappedSession = wrapNamespace("stealth", session, trace);
604
+ const redirects = wrapStealthRedirects(session.redirects, trace);
605
+ return new Proxy(wrappedSession, {
606
+ get(target, property, receiver) {
607
+ return property === "redirects" ? redirects : Reflect.get(target, property, receiver);
608
+ },
609
+ });
610
+ }
611
+
258
612
  function wrapNamespace<T extends object>(
259
613
  namespace: InstrumentedNamespace,
260
614
  target: T,
261
615
  trace: TraceContext,
616
+ shouldInstrument?: (methodName: string, args: unknown[]) => boolean,
262
617
  ): T {
263
618
  const recorder = getTraceRecorder(trace);
264
619
  if (!recorder) {
@@ -310,18 +665,14 @@ function wrapNamespace<T extends object>(
310
665
  namespaceTarget &&
311
666
  typeof namespaceTarget === "object" &&
312
667
  "engine" in namespaceTarget &&
313
- typeof (namespaceTarget as { engine?: unknown }).engine ===
314
- "string"
668
+ typeof (namespaceTarget as { engine?: unknown }).engine === "string"
315
669
  ) {
316
- attributes.engine = (
317
- namespaceTarget as { engine: string }
318
- ).engine;
670
+ attributes.engine = (namespaceTarget as { engine: string }).engine;
319
671
  }
320
672
 
321
673
  return attributes;
322
674
  },
323
- onError: (error) =>
324
- getBrowserPageAttributes("newPage", args, undefined, error),
675
+ onError: (error) => getBrowserPageAttributes("newPage", args, undefined, error),
325
676
  },
326
677
  );
327
678
  };
@@ -348,10 +699,8 @@ function wrapNamespace<T extends object>(
348
699
  return result;
349
700
  },
350
701
  {
351
- onSuccess: () =>
352
- getBrowserPageAttributes(methodName, args, elapsedMs),
353
- onError: (error) =>
354
- getBrowserPageAttributes(methodName, args, undefined, error),
702
+ onSuccess: () => getBrowserPageAttributes(methodName, args, elapsedMs),
703
+ onError: (error) => getBrowserPageAttributes(methodName, args, undefined, error),
355
704
  },
356
705
  );
357
706
  };
@@ -360,23 +709,103 @@ function wrapNamespace<T extends object>(
360
709
  return wrapped;
361
710
  }
362
711
 
363
- const wrapped = (...args: unknown[]) =>
364
- recorder.runSpan(
365
- `${namespace}.${methodName}`,
366
- () => Reflect.apply(value, namespaceTarget, args),
367
- {
368
- onSuccess: (result) =>
369
- buildSpanAttributes(namespace, methodName, args, result),
370
- onError: (error) =>
371
- buildSpanAttributes(
372
- namespace,
373
- methodName,
374
- args,
375
- undefined,
376
- error,
377
- ),
378
- },
379
- );
712
+ const wrapped = (...args: unknown[]) => {
713
+ if (shouldInstrument && !shouldInstrument(methodName, args)) {
714
+ return Reflect.apply(value, namespaceTarget, args);
715
+ }
716
+ const requestDiagnostics = snapshotRequestDiagnostics(namespace, methodName, args);
717
+ // Invoke first and decide by the RETURN VALUE. `runSpan` always
718
+ // returns a Promise, so unconditionally span-wrapping every member
719
+ // silently rewrote synchronous contracts: `ctx.state.namespace()`
720
+ // (a sync factory) came back as a Promise, and every method call on
721
+ // it threw a raw TypeError — surfaced in production as catchtable's
722
+ // deterministic CONFIRM_STATE_UNAVAILABLE on 2026-07-27 (and the
723
+ // 2026-07-22 reserve internal_error loop before it). Sync members
724
+ // keep their sync contract; only genuinely async operations are
725
+ // recorded as spans.
726
+ let result: unknown;
727
+ try {
728
+ result = Reflect.apply(value, namespaceTarget, args);
729
+ } catch (error) {
730
+ const sanitizedError = sanitizeRequestError(error, requestDiagnostics);
731
+ // A promise-returning implementation may still throw
732
+ // SYNCHRONOUSLY during pre-flight validation. Preserve the
733
+ // synchronous throw contract, but keep recording the failure
734
+ // span (the pre-fidelity wrapper captured these).
735
+ recorder
736
+ .runSpan(
737
+ `${namespace}.${methodName}`,
738
+ () => {
739
+ throw sanitizedError;
740
+ },
741
+ {
742
+ onError: (spanError) =>
743
+ buildSpanAttributes(
744
+ namespace,
745
+ methodName,
746
+ args,
747
+ undefined,
748
+ spanError,
749
+ requestDiagnostics,
750
+ ),
751
+ },
752
+ )
753
+ .catch(() => undefined);
754
+ throw sanitizedError;
755
+ }
756
+ if (!isThenable(result)) {
757
+ // The state namespace factory returns the object whose METHODS
758
+ // are the operations worth tracing — instrument that object so
759
+ // `state.get`/`state.compareAndSet`/… spans exist (they never
760
+ // could before: the factory's return value was destroyed).
761
+ if (
762
+ namespace === "state" &&
763
+ methodName === "namespace" &&
764
+ typeof result === "object" &&
765
+ result !== null
766
+ ) {
767
+ return wrapNamespace(namespace, result, trace);
768
+ }
769
+ if (
770
+ namespace === "stealth" &&
771
+ methodName === "createSession" &&
772
+ typeof result === "object" &&
773
+ result !== null
774
+ ) {
775
+ return wrapStealthSession(result as StealthSession, trace);
776
+ }
777
+ return result;
778
+ }
779
+ const sanitizedResult = Promise.resolve(result).catch((error: unknown) => {
780
+ throw sanitizeRequestError(error, requestDiagnostics);
781
+ });
782
+ const tracedResult = recorder.runSpan(`${namespace}.${methodName}`, () => sanitizedResult, {
783
+ onSuccess: (spanResult) =>
784
+ buildSpanAttributes(
785
+ namespace,
786
+ methodName,
787
+ args,
788
+ spanResult,
789
+ undefined,
790
+ requestDiagnostics,
791
+ ),
792
+ onError: (error) =>
793
+ buildSpanAttributes(namespace, methodName, args, undefined, error, requestDiagnostics),
794
+ });
795
+ return namespace === "http" && methodName === "stream"
796
+ ? tracedResult.then((spanResult) =>
797
+ isHttpStreamResponse(spanResult)
798
+ ? instrumentHttpStreamConsumption(spanResult, recorder, args, requestDiagnostics)
799
+ : spanResult,
800
+ )
801
+ : namespace === "http" && methodName === "sse"
802
+ ? tracedResult.then((spanResult) =>
803
+ isAsyncIterable<SseMessage>(spanResult)
804
+ ? instrumentHttpSseConsumption(spanResult, recorder, args, requestDiagnostics)
805
+ : spanResult,
806
+ )
807
+ : tracedResult;
808
+ };
380
809
 
381
810
  wrappedMethods.set(property, wrapped);
382
811
  return wrapped;