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

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 (85) hide show
  1. package/AUTHORING.md +17 -16
  2. package/CHANGELOG.md +8 -0
  3. package/bin/apifuse-dev.ts +5 -7
  4. package/bin/apifuse-pack-types.ts +26 -0
  5. package/bin/apifuse-perf.ts +4 -6
  6. package/bin/apifuse-record.ts +4 -7
  7. package/bin/apifuse-submit-check.ts +2 -2
  8. package/dist/define.d.ts +3 -2
  9. package/dist/define.js +1 -1
  10. package/dist/dev.js +3 -1
  11. package/dist/engine.d.ts +7 -6
  12. package/dist/engine.js +3 -4
  13. package/dist/error-resolution.js +21 -4
  14. package/dist/errors.d.ts +3 -0
  15. package/dist/errors.js +9 -0
  16. package/dist/handle-meta.d.ts +55 -0
  17. package/dist/handle-meta.js +57 -0
  18. package/dist/handle.d.ts +208 -0
  19. package/dist/handle.js +299 -0
  20. package/dist/i18n/catalog.js +21 -1
  21. package/dist/index.d.ts +7 -4
  22. package/dist/index.js +6 -3
  23. package/dist/lint.d.ts +3 -0
  24. package/dist/lint.js +372 -47
  25. package/dist/provider.d.ts +5 -3
  26. package/dist/provider.js +4 -2
  27. package/dist/runtime/handle-wordlist.d.ts +43 -0
  28. package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
  29. package/dist/runtime/handle.d.ts +71 -0
  30. package/dist/runtime/handle.js +900 -0
  31. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  32. package/dist/runtime/http-telemetry-binding.js +152 -0
  33. package/dist/runtime/http-telemetry-guard.d.ts +6 -0
  34. package/dist/runtime/http-telemetry-guard.js +150 -0
  35. package/dist/runtime/http-telemetry.d.ts +104 -0
  36. package/dist/runtime/http-telemetry.js +190 -0
  37. package/dist/runtime/http.d.ts +31 -0
  38. package/dist/runtime/http.js +222 -146
  39. package/dist/runtime/native-network.d.ts +10 -1
  40. package/dist/runtime/native-network.js +230 -29
  41. package/dist/runtime/native-telemetry.d.ts +142 -0
  42. package/dist/runtime/native-telemetry.js +231 -0
  43. package/dist/runtime/state.d.ts +4 -1
  44. package/dist/runtime/state.js +32 -17
  45. package/dist/server/index.d.ts +3 -0
  46. package/dist/server/index.js +2 -0
  47. package/dist/server/sensitive-values.d.ts +1 -1
  48. package/dist/server/sensitive-values.js +0 -3
  49. package/dist/server/serve-implementation.d.ts +6 -11
  50. package/dist/server/serve-implementation.js +119 -52
  51. package/dist/testing/run.js +10 -7
  52. package/dist/types.d.ts +4 -107
  53. package/package.json +1 -1
  54. package/src/define.ts +4 -3
  55. package/src/dev.ts +3 -1
  56. package/src/engine.ts +5 -6
  57. package/src/error-resolution.ts +21 -4
  58. package/src/errors.ts +13 -0
  59. package/src/handle-meta.ts +93 -0
  60. package/src/handle.ts +612 -0
  61. package/src/i18n/catalog.ts +23 -1
  62. package/src/index.ts +45 -13
  63. package/src/lint.ts +437 -55
  64. package/src/provider.ts +19 -20
  65. package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
  66. package/src/runtime/handle.ts +1172 -0
  67. package/src/runtime/http-telemetry-binding.ts +162 -0
  68. package/src/runtime/http-telemetry-guard.ts +155 -0
  69. package/src/runtime/http-telemetry.ts +292 -0
  70. package/src/runtime/http.ts +295 -173
  71. package/src/runtime/native-network.ts +281 -33
  72. package/src/runtime/native-telemetry.ts +366 -0
  73. package/src/runtime/state.ts +44 -16
  74. package/src/server/index.ts +30 -0
  75. package/src/server/sensitive-values.ts +0 -3
  76. package/src/server/serve-implementation.ts +127 -89
  77. package/src/testing/run.ts +11 -7
  78. package/src/types.ts +4 -137
  79. package/dist/choice-token.d.ts +0 -24
  80. package/dist/choice-token.js +0 -74
  81. package/dist/runtime/choice-wordlist.d.ts +0 -9
  82. package/dist/runtime/choice.d.ts +0 -27
  83. package/dist/runtime/choice.js +0 -801
  84. package/src/choice-token.ts +0 -165
  85. package/src/runtime/choice.ts +0 -1197
@@ -11,6 +11,7 @@ import {
11
11
  type ProxyProtocol,
12
12
  VENDOR_DEFAULT_PROTOCOL,
13
13
  resolveWithVendor,
14
+ type ProxyTelemetrySink,
14
15
  } from "../config/loader.js";
15
16
  import {
16
17
  type DynamicEgressRuleSnapshot,
@@ -61,6 +62,12 @@ import {
61
62
  synthesizeNodemavenProxy,
62
63
  } from "./proxy-nodemaven.js";
63
64
  import { redactSensitiveError } from "./request-options.js";
65
+ import type {
66
+ NativeConnectTelemetryEvent,
67
+ NativeTelemetryDiagnostics,
68
+ NativeTelemetryErrorCode,
69
+ NativeTelemetrySink,
70
+ } from "./native-telemetry.js";
64
71
 
65
72
  export {
66
73
  NativeEgressGrantExpiredError,
@@ -82,6 +89,8 @@ export type NativeGatewayProxySynthesisInput = {
82
89
  readonly now: number;
83
90
  readonly protocol: ProxyProtocol;
84
91
  readonly credentials: VendorCredentialResolver;
92
+ /** Existing proxy lease telemetry; adapters may report detailed allocation facts. */
93
+ readonly telemetry?: ProxyTelemetrySink;
85
94
  };
86
95
 
87
96
  export type VendorCredentialLookup =
@@ -113,9 +122,15 @@ export type NativeGatewayProxyResolutionInput = {
113
122
  readonly protocol?: ProxyProtocol;
114
123
  readonly credentials?: VendorCredentialResolver;
115
124
  readonly gatewaySynthesizers?: readonly NativeGatewayProxySynthesizer[];
125
+ readonly telemetry?: ProxyTelemetrySink;
126
+ readonly nativeTelemetry?: NativeTelemetrySink;
116
127
  };
117
128
 
118
129
  export type NativeNetworkClientOptions = {
130
+ /** Existing proxy lease contributor, shared with HTTP and stealth. */
131
+ readonly telemetry?: ProxyTelemetrySink;
132
+ /** Request-scoped connection, byte and lifecycle observations. */
133
+ readonly nativeTelemetry?: NativeTelemetrySink;
119
134
  readonly proxyPolicy?: ProviderProxyPolicy;
120
135
  readonly affinityKey?: string;
121
136
  /** Stable credential/account identity; hashed before vendor synthesis. */
@@ -238,6 +253,7 @@ async function synthesizeSmartproxyGateway(
238
253
  proxyPolicy: input.policy,
239
254
  affinityKey: input.affinityKey,
240
255
  protocol: input.protocol,
256
+ telemetry: input.telemetry,
241
257
  },
242
258
  {
243
259
  protocol: input.protocol,
@@ -257,6 +273,9 @@ async function synthesizeSmartproxyGateway(
257
273
  sticky: isStickyPolicy(input.policy),
258
274
  };
259
275
  } catch (error) {
276
+ if (error instanceof ProxyResolutionError && error.telemetry) {
277
+ emitTelemetry(() => input.telemetry?.recordProxyResolution(error.telemetry!));
278
+ }
260
279
  const cause = error instanceof Error ? error : new Error(String(error));
261
280
  return skipped({
262
281
  kind: "allocation_failed",
@@ -345,6 +364,61 @@ function defaultVendorProtocol(vendor: ProviderProxyProvider): ProxyProtocol {
345
364
  : "http";
346
365
  }
347
366
 
367
+ // Instrumentation must not change the transport's exceptions or vendor ordering.
368
+ function emitTelemetry(emit: () => void): void {
369
+ try {
370
+ emit();
371
+ } catch {
372
+ /* An observer cannot fail an underlying operation. */
373
+ }
374
+ }
375
+
376
+ function nativeErrorCode(error: unknown): NativeTelemetryErrorCode {
377
+ const code =
378
+ error instanceof NativeNetworkError || error instanceof ProxyResolutionError
379
+ ? error.code
380
+ : undefined;
381
+ switch (code) {
382
+ case "native_connection_aborted":
383
+ case "native_connection_closed":
384
+ case "native_connection_failed":
385
+ case "native_connection_idle_timeout":
386
+ case "native_connection_timeout":
387
+ case "native_egress_authorization_failed":
388
+ case "native_egress_grant_expired":
389
+ case "native_egress_grant_invalid":
390
+ case "native_egress_grant_limit_exceeded":
391
+ case "native_egress_input_invalid":
392
+ case "native_egress_not_declared":
393
+ case "native_egress_policy_invalid":
394
+ case "native_dynamic_egress_unsupported":
395
+ case "native_proxy_expired":
396
+ case "native_proxy_invalid":
397
+ case "PROXY_REQUIRED":
398
+ case "PROXY_ALLOCATION_FAILED":
399
+ return code;
400
+ default:
401
+ return "other";
402
+ }
403
+ }
404
+
405
+ function nativeErrorDiagnostics(error: unknown): NativeTelemetryDiagnostics {
406
+ if (!(error instanceof Error)) return {};
407
+ const cause = error.cause instanceof Error ? error.cause : undefined;
408
+ const detail = cause ?? error;
409
+ const systemCode: unknown = Reflect.get(detail, "code");
410
+ const status: unknown = Reflect.get(detail, "connectStatusCode");
411
+ const socksReplyCode: unknown = Reflect.get(detail, "socks5ReplyCode");
412
+ return {
413
+ errorName: error.name,
414
+ errorMessage: error.message,
415
+ ...(cause ? { causeName: cause.name, causeMessage: cause.message } : {}),
416
+ ...(typeof systemCode === "string" ? { systemCode } : {}),
417
+ ...(typeof status === "number" ? { status } : {}),
418
+ ...(typeof socksReplyCode === "number" ? { socksReplyCode } : {}),
419
+ };
420
+ }
421
+
348
422
  async function resolveNativeGatewayProxyDetailed(
349
423
  input: NativeGatewayProxyResolutionInput,
350
424
  ): Promise<{ proxy?: NativeGatewayProxy; skips: readonly NativeGatewayVendorSkip[] }> {
@@ -353,14 +427,67 @@ async function resolveNativeGatewayProxyDetailed(
353
427
  const credentials = input.credentials ?? createEnvVendorCredentialResolver();
354
428
  const now = input.now ?? Date.now();
355
429
  const skips: NativeGatewayVendorSkip[] = [];
356
- for (const vendor of resolveNativeVendorChain(input.policy)) {
430
+ const chain = resolveNativeVendorChain(input.policy);
431
+ const recordSkip = (skip: NativeGatewayVendorSkip): void => {
432
+ skips.push(skip);
433
+ emitTelemetry(() =>
434
+ input.nativeTelemetry?.recordVendorSkip({
435
+ vendor: skip.vendor,
436
+ reason: skip.reason.kind,
437
+ diagnostics:
438
+ skip.reason.kind === "credentials_absent"
439
+ ? { missingFields: skip.reason.missing }
440
+ : skip.reason.kind === "protocol_unsupported"
441
+ ? { protocol: skip.reason.protocol }
442
+ : skip.reason.kind === "allocation_failed" ||
443
+ skip.reason.kind === "credential_lookup_failed"
444
+ ? nativeErrorDiagnostics(skip.reason.cause)
445
+ : {},
446
+ }),
447
+ );
448
+ if (skip.vendor === "smartproxy" || skip.vendor === "nodemaven") {
449
+ const vendor = skip.vendor;
450
+ const next = chain[chain.indexOf(vendor) + 1];
451
+ emitTelemetry(() =>
452
+ input.telemetry?.recordProxyVendorFailover?.({
453
+ vendor,
454
+ ...(next === "smartproxy" || next === "nodemaven" ? { nextVendor: next } : {}),
455
+ phase: "resolution",
456
+ reason:
457
+ skip.reason.kind === "credentials_absent"
458
+ ? "no_credentials"
459
+ : skip.reason.kind === "protocol_unsupported"
460
+ ? "protocol_unsupported"
461
+ : "allocation_failed",
462
+ }),
463
+ );
464
+ }
465
+ };
466
+ for (const vendor of chain) {
357
467
  const protocol = input.protocol ?? defaultVendorProtocol(vendor);
358
468
  if (!isProxyProtocol(protocol)) {
359
- skips.push({ vendor, reason: { kind: "protocol_unsupported", protocol: String(protocol) } });
469
+ recordSkip({ vendor, reason: { kind: "protocol_unsupported", protocol: String(protocol) } });
360
470
  continue;
361
471
  }
472
+ let resolutionRecorded = false;
473
+ const telemetry: ProxyTelemetrySink | undefined = input.telemetry
474
+ ? {
475
+ recordProxyResolution(event) {
476
+ resolutionRecorded = true;
477
+ emitTelemetry(() => input.telemetry?.recordProxyResolution(event));
478
+ },
479
+ recordProxyAttempt(event) {
480
+ emitTelemetry(() => input.telemetry?.recordProxyAttempt?.(event));
481
+ },
482
+ recordProxyVendorFailover(event) {
483
+ emitTelemetry(() => input.telemetry?.recordProxyVendorFailover?.(event));
484
+ },
485
+ }
486
+ : undefined;
362
487
  let vendorSkip: NativeGatewayVendorSkip["reason"] | undefined;
363
488
  for (const synthesize of synthesizers) {
489
+ const startedAt = Date.now();
490
+ resolutionRecorded = false;
364
491
  try {
365
492
  const resolved = await synthesize({
366
493
  vendor,
@@ -369,6 +496,7 @@ async function resolveNativeGatewayProxyDetailed(
369
496
  now,
370
497
  protocol,
371
498
  credentials,
499
+ telemetry,
372
500
  });
373
501
  if (!resolved) continue;
374
502
  if (isSkippedSynthesis(resolved)) {
@@ -377,6 +505,19 @@ async function resolveNativeGatewayProxyDetailed(
377
505
  }
378
506
  if (resolved.vendor === vendor) {
379
507
  assertTunnelingScheme(resolved.url);
508
+ if (!resolutionRecorded && (vendor === "smartproxy" || vendor === "nodemaven")) {
509
+ const resolvedProtocol = new URL(resolved.url).protocol === "http:" ? "http" : "socks5";
510
+ emitTelemetry(() =>
511
+ telemetry?.recordProxyResolution({
512
+ provider: vendor,
513
+ protocol: resolvedProtocol,
514
+ cacheStatus: "disabled",
515
+ cacheHit: false,
516
+ resolutionMs: Math.max(0, Date.now() - startedAt),
517
+ attempts: 1,
518
+ }),
519
+ );
520
+ }
380
521
  return { proxy: resolved, skips };
381
522
  }
382
523
  } catch (error) {
@@ -386,7 +527,7 @@ async function resolveNativeGatewayProxyDetailed(
386
527
  };
387
528
  }
388
529
  }
389
- skips.push({ vendor, reason: vendorSkip ?? { kind: "adapter_unavailable" } });
530
+ recordSkip({ vendor, reason: vendorSkip ?? { kind: "adapter_unavailable" } });
390
531
  }
391
532
  return { skips };
392
533
  }
@@ -908,6 +1049,12 @@ export function createNativeNetworkConnection(
908
1049
  idleTimer = undefined;
909
1050
  if (socket.readableEnded || socket.destroyed) return;
910
1051
  closeReason = new NativeIdleTimeoutError();
1052
+ emitTelemetry(() =>
1053
+ options.nativeTelemetry?.recordLifecycle({
1054
+ kind: "idle",
1055
+ errorCode: "native_connection_idle_timeout",
1056
+ }),
1057
+ );
911
1058
  socket.destroy(closeReason);
912
1059
  },
913
1060
  Math.max(0, idleTimeoutMs),
@@ -924,6 +1071,13 @@ export function createNativeNetworkConnection(
924
1071
  };
925
1072
  socket.on("error", (error) => {
926
1073
  terminalError = error;
1074
+ if (!closeReason)
1075
+ emitTelemetry(() =>
1076
+ options.nativeTelemetry?.recordError({
1077
+ errorCode: "native_connection_failed",
1078
+ diagnostics: nativeErrorDiagnostics(error),
1079
+ }),
1080
+ );
927
1081
  });
928
1082
  socket.once("end", clearIdleTimer);
929
1083
  socket.once("close", clearLifecycle);
@@ -948,15 +1102,35 @@ export function createNativeNetworkConnection(
948
1102
  const leadDelay = Math.max(0, remaining - leadSeconds * 1_000);
949
1103
  expiringTimer = setTimeout(() => {
950
1104
  expiringTimer = undefined;
1105
+ emitTelemetry(() => options.nativeTelemetry?.recordLifecycle({ kind: "drain" }));
951
1106
  const handler = drainHandler;
952
1107
  if (!handler) {
1108
+ emitTelemetry(() =>
1109
+ options.nativeTelemetry?.recordLifecycle({ kind: "drain_missing_handler" }),
1110
+ );
953
1111
  warn("Native sticky proxy is expiring without a drain handler");
954
1112
  return;
955
1113
  }
956
1114
  void (async () => {
957
1115
  try {
958
- await Promise.race([Promise.resolve(handler(event)), lifecycleCutoff]);
959
- } catch {
1116
+ await Promise.race([
1117
+ Promise.resolve(handler(event)).then(() => {
1118
+ if (socket.destroyed || lifecycleSettled) return;
1119
+ emitTelemetry(() =>
1120
+ options.nativeTelemetry?.recordLifecycle({ kind: "drain_acknowledged" }),
1121
+ );
1122
+ }),
1123
+ lifecycleCutoff,
1124
+ ]);
1125
+ } catch (error) {
1126
+ if (socket.destroyed || lifecycleSettled) return;
1127
+ emitTelemetry(() =>
1128
+ options.nativeTelemetry?.recordLifecycle({
1129
+ kind: "drain_error",
1130
+ errorCode: "other",
1131
+ diagnostics: nativeErrorDiagnostics(error),
1132
+ }),
1133
+ );
960
1134
  // Drain failures do not bypass the hard-expiry fail-safe.
961
1135
  }
962
1136
  })();
@@ -966,6 +1140,13 @@ export function createNativeNetworkConnection(
966
1140
  hardExpiryTimer = undefined;
967
1141
  if (socket.destroyed) return;
968
1142
  closeReason = new NativeProxyExpiredError(expiresAt);
1143
+ emitTelemetry(() =>
1144
+ options.nativeTelemetry?.recordLifecycle({
1145
+ kind: "expiry",
1146
+ errorCode: "native_proxy_expired",
1147
+ diagnostics: { expiresAt },
1148
+ }),
1149
+ );
969
1150
  settleLifecycle();
970
1151
  socket.destroy();
971
1152
  }, remaining);
@@ -978,6 +1159,9 @@ export function createNativeNetworkConnection(
978
1159
  const chunk = socket.read() as Buffer | null;
979
1160
  if (chunk) {
980
1161
  resetIdleTimer();
1162
+ emitTelemetry(() =>
1163
+ options.nativeTelemetry?.recordBytes({ direction: "in", bytes: chunk.byteLength }),
1164
+ );
981
1165
  return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
982
1166
  }
983
1167
  if (socket.readableEnded || socket.destroyed) return null;
@@ -1025,6 +1209,9 @@ export function createNativeNetworkConnection(
1025
1209
  write: async (data) => {
1026
1210
  if (closeReason) throw closeReason;
1027
1211
  if (socket.destroyed) {
1212
+ emitTelemetry(() =>
1213
+ options.nativeTelemetry?.recordError({ errorCode: "native_connection_closed" }),
1214
+ );
1028
1215
  throw new NativeNetworkError("Native connection is closed", "native_connection_closed");
1029
1216
  }
1030
1217
  await new Promise<void>((resolve, reject) => {
@@ -1033,6 +1220,9 @@ export function createNativeNetworkConnection(
1033
1220
  else resolve();
1034
1221
  });
1035
1222
  });
1223
+ emitTelemetry(() =>
1224
+ options.nativeTelemetry?.recordBytes({ direction: "out", bytes: data.byteLength }),
1225
+ );
1036
1226
  },
1037
1227
  close: async () => {
1038
1228
  if (socket.closed || socket.destroyed) {
@@ -1067,6 +1257,8 @@ async function resolveConnectionProxy(
1067
1257
  protocol: options.proxyProtocol,
1068
1258
  credentials: options.credentials,
1069
1259
  gatewaySynthesizers: options.gatewaySynthesizers,
1260
+ telemetry: options.telemetry,
1261
+ nativeTelemetry: options.nativeTelemetry,
1070
1262
  }),
1071
1263
  input.signal,
1072
1264
  deadline,
@@ -1570,36 +1762,92 @@ export function createNativeNetworkClient(
1570
1762
  options: NativeNetworkClientOptions = {},
1571
1763
  ): NativeNetworkClient {
1572
1764
  const egress = createNativeEgressAuthorization(options);
1573
- return {
1574
- connectTcp: async (input) => {
1575
- const request = snapshotNativeConnectInput(input);
1576
- egress.assertConnect(request, "disabled");
1577
- const deadline = deadlineFrom(request.timeoutMs);
1578
- assertCanStart(request.signal, deadline);
1579
- const proxy = await resolveConnectionProxy(options, request, deadline);
1580
- egress.assertConnect(request, "disabled");
1581
- const socket = proxy
1582
- ? await connectProxyTunnel(proxy, request, deadline, () =>
1583
- egress.assertConnect(request, "disabled"),
1584
- )
1585
- : await connectPlainSocket(request.host, request.port, request.signal, deadline);
1586
- return createNativeNetworkConnection(socket, proxy, options, request.idleTimeoutMs);
1587
- },
1588
- connectTls: async (input) => {
1589
- const request = snapshotNativeConnectInput(input);
1590
- egress.assertConnect(request, "required");
1765
+ const connect = async (
1766
+ input: NativeNetworkConnectInput,
1767
+ kind: "tcp" | "tls",
1768
+ ): Promise<NativeNetworkConnection> => {
1769
+ const startedAt = Date.now();
1770
+ let proxy: NativeGatewayProxy | undefined;
1771
+ let request: NativeNetworkConnectInput | undefined;
1772
+ let tunnelMs = 0;
1773
+ let outcome: NativeConnectTelemetryEvent["outcome"] = "error";
1774
+ let failure: unknown;
1775
+ try {
1776
+ request = snapshotNativeConnectInput(input);
1777
+ const tls = kind === "tls" ? "required" : "disabled";
1778
+ egress.assertConnect(request, tls);
1591
1779
  const deadline = deadlineFrom(request.timeoutMs);
1592
1780
  assertCanStart(request.signal, deadline);
1593
- const proxy = await resolveConnectionProxy(options, request, deadline);
1594
- egress.assertConnect(request, "required");
1595
- const tunnel = proxy
1596
- ? await connectProxyTunnel(proxy, request, deadline, () =>
1597
- egress.assertConnect(request, "required"),
1598
- )
1599
- : undefined;
1600
- const socket = await upgradeTls(tunnel, request, deadline);
1601
- return createNativeNetworkConnection(socket, proxy, options, request.idleTimeoutMs);
1602
- },
1781
+ proxy = await resolveConnectionProxy(options, request, deadline);
1782
+ egress.assertConnect(request, tls);
1783
+ let tunnel: Socket | undefined;
1784
+ if (proxy) {
1785
+ const tunnelStartedAt = Date.now();
1786
+ try {
1787
+ const destination = request;
1788
+ tunnel = await connectProxyTunnel(proxy, request, deadline, () =>
1789
+ egress.assertConnect(destination, tls),
1790
+ );
1791
+ } finally {
1792
+ tunnelMs = Math.max(0, Date.now() - tunnelStartedAt);
1793
+ }
1794
+ }
1795
+ const socket =
1796
+ kind === "tls"
1797
+ ? await upgradeTls(tunnel, request, deadline)
1798
+ : (tunnel ??
1799
+ (await connectPlainSocket(request.host, request.port, request.signal, deadline)));
1800
+ const connection = createNativeNetworkConnection(
1801
+ socket,
1802
+ proxy,
1803
+ options,
1804
+ request.idleTimeoutMs,
1805
+ );
1806
+ outcome = "ok";
1807
+ return connection;
1808
+ } catch (error) {
1809
+ failure = error;
1810
+ throw error;
1811
+ } finally {
1812
+ // One terminal sample for this connect call, including preflight and rethrows.
1813
+ emitTelemetry(() =>
1814
+ options.nativeTelemetry?.recordConnect({
1815
+ kind,
1816
+ outcome,
1817
+ ms: Math.max(0, Date.now() - startedAt),
1818
+ tunnelMs,
1819
+ proxyUsed: proxy !== undefined,
1820
+ ...(proxy ? { vendor: proxy.vendor } : {}),
1821
+ ...(outcome === "error" ? { errorCode: nativeErrorCode(failure) } : {}),
1822
+ diagnostics: {
1823
+ ...(request
1824
+ ? {
1825
+ host: request.host,
1826
+ port: request.port,
1827
+ ...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
1828
+ ...(request.idleTimeoutMs === undefined
1829
+ ? {}
1830
+ : { idleTimeoutMs: request.idleTimeoutMs }),
1831
+ ...(request.serverName ? { serverName: request.serverName } : {}),
1832
+ }
1833
+ : {}),
1834
+ ...(proxy
1835
+ ? {
1836
+ protocol: new URL(proxy.url).protocol,
1837
+ sticky: proxy.sticky,
1838
+ ...(proxy.sessionId ? { sessionId: proxy.sessionId } : {}),
1839
+ ...(proxy.expiresAt ? { expiresAt: proxy.expiresAt } : {}),
1840
+ }
1841
+ : {}),
1842
+ ...nativeErrorDiagnostics(failure),
1843
+ },
1844
+ }),
1845
+ );
1846
+ }
1847
+ };
1848
+ return {
1849
+ connectTcp: (input) => connect(input, "tcp"),
1850
+ connectTls: (input) => connect(input, "tls"),
1603
1851
  grantTcpEgress: (input) => egress.grant(snapshotNativeGrantInput(input)),
1604
1852
  };
1605
1853
  }