@apifuse/provider-sdk 2.2.0-beta.11 → 2.2.0-beta.13

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 (59) hide show
  1. package/AUTHORING.md +238 -0
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +44 -2
  4. package/bin/apifuse-pack-smoke.ts +14 -0
  5. package/bin/apifuse-pack-types.ts +40 -1
  6. package/bin/apifuse-record.ts +622 -57
  7. package/bin/apifuse-submit-check.ts +43 -10
  8. package/dist/config/loader.d.ts +9 -1
  9. package/dist/config/loader.js +9 -0
  10. package/dist/define.d.ts +2 -1
  11. package/dist/define.js +61 -3
  12. package/dist/errors.d.ts +5 -0
  13. package/dist/errors.js +15 -0
  14. package/dist/fixture-sanitization.d.ts +26 -0
  15. package/dist/fixture-sanitization.js +216 -0
  16. package/dist/index.d.ts +4 -3
  17. package/dist/index.js +2 -1
  18. package/dist/provider.d.ts +2 -1
  19. package/dist/provider.js +1 -0
  20. package/dist/runtime/http.js +86 -32
  21. package/dist/runtime/instrumentation.js +295 -9
  22. package/dist/runtime/native-network.d.ts +53 -0
  23. package/dist/runtime/native-network.js +477 -0
  24. package/dist/runtime/proxy-nodemaven.d.ts +14 -0
  25. package/dist/runtime/proxy-nodemaven.js +20 -2
  26. package/dist/runtime/request-options.d.ts +68 -1
  27. package/dist/runtime/request-options.js +548 -0
  28. package/dist/runtime/stealth.d.ts +3 -1
  29. package/dist/runtime/stealth.js +352 -86
  30. package/dist/server/index.d.ts +1 -1
  31. package/dist/server/index.js +1 -1
  32. package/dist/server/self-test-input-tokens.d.ts +2 -1
  33. package/dist/server/self-test-input-tokens.js +18 -14
  34. package/dist/stream-evidence.d.ts +74 -0
  35. package/dist/stream-evidence.js +785 -0
  36. package/dist/testing/index.d.ts +1 -1
  37. package/dist/testing/index.js +1 -1
  38. package/dist/testing/run.d.ts +32 -2
  39. package/dist/testing/run.js +451 -19
  40. package/dist/types.d.ts +201 -7
  41. package/package.json +3 -1
  42. package/src/config/loader.ts +22 -1
  43. package/src/define.ts +81 -3
  44. package/src/errors.ts +15 -0
  45. package/src/fixture-sanitization.ts +247 -0
  46. package/src/index.ts +45 -1
  47. package/src/provider.ts +37 -0
  48. package/src/runtime/http.ts +144 -38
  49. package/src/runtime/instrumentation.ts +424 -8
  50. package/src/runtime/native-network.ts +600 -0
  51. package/src/runtime/proxy-nodemaven.ts +37 -2
  52. package/src/runtime/request-options.ts +680 -1
  53. package/src/runtime/stealth.ts +420 -88
  54. package/src/server/index.ts +4 -1
  55. package/src/server/self-test-input-tokens.ts +29 -14
  56. package/src/stream-evidence.ts +988 -0
  57. package/src/testing/index.ts +9 -1
  58. package/src/testing/run.ts +608 -12
  59. package/src/types.ts +235 -7
@@ -1,4 +1,22 @@
1
- import type { ProviderContext } from "../types.js";
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,
@@ -15,6 +33,16 @@ export type InstrumentedProviderContext<T extends ProviderContext> = Omit<T, "tr
15
33
  type InstrumentedNamespace = "http" | "stealth" | "browser" | "session" | "state";
16
34
 
17
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
+ };
18
46
 
19
47
  function isThenable(value: unknown): value is PromiseLike<unknown> {
20
48
  return (
@@ -92,12 +120,231 @@ function getUrl(
92
120
  return undefined;
93
121
  }
94
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
+
95
340
  function getMethod(
96
341
  namespace: InstrumentedNamespace,
97
342
  methodName: string,
98
343
  args: unknown[],
99
344
  ): string | undefined {
100
345
  if (namespace === "http") {
346
+ // Preserve the original instrumentation contract: generic entry points are
347
+ // reported as REQUEST/STREAM/SSE, independent of transport options.
101
348
  return methodName.toUpperCase();
102
349
  }
103
350
 
@@ -119,9 +366,19 @@ function buildSpanAttributes(
119
366
  args: unknown[],
120
367
  result?: unknown,
121
368
  error?: unknown,
369
+ diagnostics: RequestDiagnostics = { sensitiveParamNames: [], sensitiveValues: [] },
122
370
  ): Record<string, string | number | boolean> {
123
371
  const attributes: Record<string, string | number | boolean> = {};
124
- 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;
125
382
  const method = getMethod(namespace, methodName, args);
126
383
  const status = error ? getErrorStatus(error) : getResponseStatus(namespace, result);
127
384
  const duration = error ? undefined : getResponseDuration(result);
@@ -129,6 +386,8 @@ function buildSpanAttributes(
129
386
  if (url) {
130
387
  attributes.url = url;
131
388
  }
389
+ if (diagnostics.requestId) attributes.request_id = diagnostics.requestId;
390
+ if (diagnostics.diagnosticUrlDegraded) attributes.redaction_degraded = true;
132
391
 
133
392
  if (method) {
134
393
  attributes.method = method;
@@ -153,6 +412,64 @@ function buildSpanAttributes(
153
412
  return attributes;
154
413
  }
155
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
+
156
473
  function getBrowserPageAttributes(
157
474
  methodName: string,
158
475
  args: unknown[],
@@ -238,10 +555,65 @@ function wrapPage<T extends object>(page: T, trace: TraceContext): T {
238
555
  });
239
556
  }
240
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
+
241
612
  function wrapNamespace<T extends object>(
242
613
  namespace: InstrumentedNamespace,
243
614
  target: T,
244
615
  trace: TraceContext,
616
+ shouldInstrument?: (methodName: string, args: unknown[]) => boolean,
245
617
  ): T {
246
618
  const recorder = getTraceRecorder(trace);
247
619
  if (!recorder) {
@@ -338,6 +710,10 @@ function wrapNamespace<T extends object>(
338
710
  }
339
711
 
340
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);
341
717
  // Invoke first and decide by the RETURN VALUE. `runSpan` always
342
718
  // returns a Promise, so unconditionally span-wrapping every member
343
719
  // silently rewrote synchronous contracts: `ctx.state.namespace()`
@@ -351,6 +727,7 @@ function wrapNamespace<T extends object>(
351
727
  try {
352
728
  result = Reflect.apply(value, namespaceTarget, args);
353
729
  } catch (error) {
730
+ const sanitizedError = sanitizeRequestError(error, requestDiagnostics);
354
731
  // A promise-returning implementation may still throw
355
732
  // SYNCHRONOUSLY during pre-flight validation. Preserve the
356
733
  // synchronous throw contract, but keep recording the failure
@@ -359,15 +736,22 @@ function wrapNamespace<T extends object>(
359
736
  .runSpan(
360
737
  `${namespace}.${methodName}`,
361
738
  () => {
362
- throw error;
739
+ throw sanitizedError;
363
740
  },
364
741
  {
365
742
  onError: (spanError) =>
366
- buildSpanAttributes(namespace, methodName, args, undefined, spanError),
743
+ buildSpanAttributes(
744
+ namespace,
745
+ methodName,
746
+ args,
747
+ undefined,
748
+ spanError,
749
+ requestDiagnostics,
750
+ ),
367
751
  },
368
752
  )
369
753
  .catch(() => undefined);
370
- throw error;
754
+ throw sanitizedError;
371
755
  }
372
756
  if (!isThenable(result)) {
373
757
  // The state namespace factory returns the object whose METHODS
@@ -382,13 +766,45 @@ function wrapNamespace<T extends object>(
382
766
  ) {
383
767
  return wrapNamespace(namespace, result, trace);
384
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
+ }
385
777
  return result;
386
778
  }
387
- return recorder.runSpan(`${namespace}.${methodName}`, () => result, {
779
+ const sanitizedResult = Promise.resolve(result).catch((error: unknown) => {
780
+ throw sanitizeRequestError(error, requestDiagnostics);
781
+ });
782
+ const tracedResult = recorder.runSpan(`${namespace}.${methodName}`, () => sanitizedResult, {
388
783
  onSuccess: (spanResult) =>
389
- buildSpanAttributes(namespace, methodName, args, spanResult),
390
- onError: (error) => buildSpanAttributes(namespace, methodName, args, undefined, error),
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),
391
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;
392
808
  };
393
809
 
394
810
  wrappedMethods.set(property, wrapped);