@apifuse/provider-sdk 2.2.0-beta.59 → 2.2.0-beta.61

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 (124) hide show
  1. package/AUTHORING.md +33 -16
  2. package/CHANGELOG.md +8 -0
  3. package/bin/apifuse-dev.ts +7 -8
  4. package/bin/apifuse-pack-types.ts +22 -0
  5. package/bin/apifuse-perf.ts +45 -10
  6. package/bin/apifuse-record.ts +6 -8
  7. package/bin/apifuse-submit-check.ts +2 -2
  8. package/dist/config/loader.js +13 -12
  9. package/dist/define.d.ts +3 -2
  10. package/dist/define.js +1 -1
  11. package/dist/dev.js +3 -1
  12. package/dist/engine.d.ts +7 -6
  13. package/dist/engine.js +6 -6
  14. package/dist/error-resolution.js +21 -4
  15. package/dist/errors.d.ts +3 -0
  16. package/dist/errors.js +9 -0
  17. package/dist/handle-meta.d.ts +39 -0
  18. package/dist/handle-meta.js +29 -0
  19. package/dist/handle.d.ts +187 -0
  20. package/dist/handle.js +254 -0
  21. package/dist/i18n/catalog.js +21 -1
  22. package/dist/index.d.ts +6 -4
  23. package/dist/index.js +4 -3
  24. package/dist/lint.d.ts +3 -0
  25. package/dist/lint.js +388 -62
  26. package/dist/provider.d.ts +4 -4
  27. package/dist/provider.js +3 -3
  28. package/dist/runtime/browser.js +2 -1
  29. package/dist/runtime/cache.js +3 -2
  30. package/dist/runtime/diagnostic-env-values.d.ts +5 -0
  31. package/dist/runtime/diagnostic-env-values.js +57 -0
  32. package/dist/runtime/diagnostic-env.d.ts +11 -0
  33. package/dist/runtime/diagnostic-env.js +35 -0
  34. package/dist/runtime/diagnostic-redactor.d.ts +49 -0
  35. package/dist/runtime/diagnostic-redactor.js +569 -0
  36. package/dist/runtime/egress-lease.d.ts +2 -0
  37. package/dist/runtime/egress-lease.js +4 -1
  38. package/dist/runtime/env.js +2 -1
  39. package/dist/runtime/handle-wordlist.d.ts +43 -0
  40. package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
  41. package/dist/runtime/handle.d.ts +71 -0
  42. package/dist/runtime/handle.js +895 -0
  43. package/dist/runtime/instrumentation.js +3 -0
  44. package/dist/runtime/native-network.d.ts +10 -1
  45. package/dist/runtime/native-network.js +230 -29
  46. package/dist/runtime/native-telemetry.d.ts +142 -0
  47. package/dist/runtime/native-telemetry.js +231 -0
  48. package/dist/runtime/ocr.js +2 -1
  49. package/dist/runtime/otlp.js +5 -4
  50. package/dist/runtime/proxy-nodemaven.js +3 -2
  51. package/dist/runtime/request-options.d.ts +7 -0
  52. package/dist/runtime/request-options.js +62 -22
  53. package/dist/runtime/resolver-vendors/browser.js +0 -1
  54. package/dist/runtime/resolver.js +3 -2
  55. package/dist/runtime/state.d.ts +4 -1
  56. package/dist/runtime/state.js +32 -17
  57. package/dist/runtime/stt.js +3 -2
  58. package/dist/runtime/trace.d.ts +5 -0
  59. package/dist/runtime/trace.js +115 -20
  60. package/dist/schema.d.ts +11 -0
  61. package/dist/schema.js +56 -2
  62. package/dist/server/index.d.ts +1 -0
  63. package/dist/server/index.js +1 -0
  64. package/dist/server/self-test-redaction.js +8 -33
  65. package/dist/server/sensitive-values.d.ts +26 -0
  66. package/dist/server/sensitive-values.js +128 -0
  67. package/dist/server/serve-implementation.d.ts +4 -11
  68. package/dist/server/serve-implementation.js +305 -96
  69. package/dist/server/trace-output.d.ts +2 -1
  70. package/dist/server/trace-output.js +46 -14
  71. package/dist/testing/run.js +10 -7
  72. package/dist/trace-sanitization.d.ts +4 -3
  73. package/dist/trace-sanitization.js +45 -22
  74. package/dist/types.d.ts +4 -107
  75. package/package.json +2 -1
  76. package/src/config/loader.ts +13 -12
  77. package/src/define.ts +4 -3
  78. package/src/dev.ts +3 -1
  79. package/src/engine.ts +8 -8
  80. package/src/error-resolution.ts +21 -4
  81. package/src/errors.ts +13 -0
  82. package/src/handle-meta.ts +58 -0
  83. package/src/handle.ts +529 -0
  84. package/src/i18n/catalog.ts +23 -1
  85. package/src/index.ts +28 -13
  86. package/src/lint.ts +456 -71
  87. package/src/provider.ts +10 -20
  88. package/src/runtime/browser.ts +2 -1
  89. package/src/runtime/cache.ts +3 -2
  90. package/src/runtime/diagnostic-env-values.ts +56 -0
  91. package/src/runtime/diagnostic-env.ts +55 -0
  92. package/src/runtime/diagnostic-redactor.ts +662 -0
  93. package/src/runtime/egress-lease.ts +6 -1
  94. package/src/runtime/env.ts +2 -1
  95. package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
  96. package/src/runtime/handle.ts +1162 -0
  97. package/src/runtime/instrumentation.ts +3 -0
  98. package/src/runtime/native-network.ts +281 -33
  99. package/src/runtime/native-telemetry.ts +366 -0
  100. package/src/runtime/ocr.ts +2 -1
  101. package/src/runtime/otlp.ts +5 -4
  102. package/src/runtime/proxy-nodemaven.ts +3 -2
  103. package/src/runtime/request-options.ts +76 -28
  104. package/src/runtime/resolver-vendors/browser.ts +0 -1
  105. package/src/runtime/resolver.ts +3 -2
  106. package/src/runtime/state.ts +44 -16
  107. package/src/runtime/stt.ts +3 -2
  108. package/src/runtime/trace.ts +168 -26
  109. package/src/schema.ts +63 -2
  110. package/src/server/index.ts +17 -0
  111. package/src/server/self-test-redaction.ts +10 -40
  112. package/src/server/sensitive-values.ts +197 -0
  113. package/src/server/serve-implementation.ts +388 -128
  114. package/src/server/trace-output.ts +77 -17
  115. package/src/testing/run.ts +11 -7
  116. package/src/trace-sanitization.ts +70 -23
  117. package/src/types.ts +4 -137
  118. package/dist/choice-token.d.ts +0 -24
  119. package/dist/choice-token.js +0 -74
  120. package/dist/runtime/choice-wordlist.d.ts +0 -9
  121. package/dist/runtime/choice.d.ts +0 -27
  122. package/dist/runtime/choice.js +0 -801
  123. package/src/choice-token.ts +0 -165
  124. package/src/runtime/choice.ts +0 -1197
@@ -1,5 +1,8 @@
1
- import { createHash } from "node:crypto";
1
+ import { NativeTelemetryCollector } from "../runtime/native-telemetry.js";
2
+ import { STATUS_CODES } from "node:http";
3
+ import { readDiagnosticEnv, withDiagnosticEnv } from "../runtime/diagnostic-env.js";
2
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
+ import { createHash } from "node:crypto";
3
6
  import { existsSync } from "node:fs";
4
7
  import { createRequire } from "node:module";
5
8
  import { join } from "node:path";
@@ -8,15 +11,15 @@ import { Hono } from "hono";
8
11
  import { z } from "zod";
9
12
  import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
10
13
  import { validateFailClosedDeclaration } from "../declaration-validation.js";
11
- import { safeProviderErrorObservability } from "../error-observability.js";
12
14
  import {
13
15
  createInProcessProviderEngine,
14
16
  ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES,
15
17
  isEngineOwnedEnvName,
16
- readEngineProxyCredentials,
17
18
  type ProviderEngine,
18
19
  type ProviderEngineBindingCandidates,
20
+ readEngineProxyCredentials,
19
21
  } from "../engine.js";
22
+ import { safeProviderErrorObservability } from "../error-observability.js";
20
23
  import {
21
24
  SDK_OWNED_PROVIDER_ERROR_CODES,
22
25
  SDK_RUNTIME_OWNED_ERROR_CODES,
@@ -42,20 +45,31 @@ import {
42
45
  import type { ProviderLocale } from "../i18n/keys.js";
43
46
  import {
44
47
  categoryForStatus,
45
- type ProviderErrorSource,
46
- sourceForCategory,
47
48
  isRetryableCategory,
48
49
  PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
49
50
  type ProviderErrorCategory,
51
+ type ProviderErrorSource,
52
+ sourceForCategory,
50
53
  } from "../observability.js";
51
54
  import { createScratchpad } from "../runtime/auth-flow.js";
52
55
  import type * as BrowserRuntimeModule from "../runtime/browser.js";
53
56
  import { createProviderCache } from "../runtime/cache.js";
54
- import {
55
- createProviderChoiceContext,
56
- PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
57
- } from "../runtime/choice.js";
57
+ import type { HandleTelemetryEvent } from "../handle.js";
58
+ import { createHandleContext } from "../runtime/handle.js";
58
59
  import { createCredentialContext } from "../runtime/credential.js";
60
+ import {
61
+ bindDiagnosticSensitiveRegistry,
62
+ type CompiledDiagnosticSensitiveValues,
63
+ compileProcessDiagnosticSensitiveValues,
64
+ createDiagnosticRedactor,
65
+ type DiagnosticRedactor,
66
+ REDACTION_FAILED,
67
+ isDiagnosticRedactionSuppressed,
68
+ bindCriticalDiagnosticRedactor,
69
+ redactCriticalDiagnosticText,
70
+ redactDiagnosticText,
71
+ registerDiagnosticSensitiveValues,
72
+ } from "../runtime/diagnostic-redactor.js";
59
73
  import {
60
74
  createCeremonyEgressLeaseRuntime,
61
75
  ENGINE_CEREMONY_EGRESS_LEASE,
@@ -65,8 +79,8 @@ import { executeOperation } from "../runtime/executor.js";
65
79
  import { createHttpClient } from "../runtime/http.js";
66
80
  import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
67
81
  import type * as NativeNetworkRuntimeModule from "../runtime/native-network.js";
68
- import { getProviderBaseUrl } from "../runtime/provider.js";
69
82
  import { createOcrClientFromEnv } from "../runtime/ocr.js";
83
+ import { getProviderBaseUrl } from "../runtime/provider.js";
70
84
  import {
71
85
  PROXY_AUTH_IP_DENIED_CODE,
72
86
  PROXY_EDGE_AUTH_REJECTED_CODE,
@@ -78,24 +92,24 @@ import {
78
92
  type ProxyTelemetryLogPayload,
79
93
  } from "../runtime/proxy-telemetry.js";
80
94
  import {
95
+ type ClosedEnum,
81
96
  closedEnum,
82
97
  RequestTelemetry,
83
- tenantOpaqueKeys,
84
- type ClosedEnum,
85
98
  type TenantNeutral,
86
99
  type TenantOpaqueKeys,
100
+ tenantOpaqueKeys,
87
101
  } from "../runtime/request-telemetry.js";
88
- import {
89
- ResolverTelemetryCollector,
90
- type ResolverTelemetryLogPayload,
91
- } from "../runtime/resolver-telemetry.js";
92
102
  import type * as ResolverRuntimeModule from "../runtime/resolver.js";
93
103
  import {
94
- createUnsupportedResolverClient,
95
104
  bindResolverTelemetry,
105
+ createUnsupportedResolverClient,
96
106
  inheritResolverTelemetryBinding,
97
107
  type ResolverSolveWithRecorder,
98
108
  } from "../runtime/resolver-shared.js";
109
+ import {
110
+ ResolverTelemetryCollector,
111
+ type ResolverTelemetryLogPayload,
112
+ } from "../runtime/resolver-telemetry.js";
99
113
  import {
100
114
  assertRequiredSecretsPresent,
101
115
  listMissingRequiredSecrets,
@@ -105,9 +119,9 @@ import {
105
119
  createProviderRuntimeStateFromEnv,
106
120
  createUnsupportedProviderRuntimeState,
107
121
  } from "../runtime/state.js";
108
- import { StealthCookieJar } from "../runtime/stealth-cookies.js";
109
122
  import type * as StealthRuntimeModule from "../runtime/stealth.js";
110
123
  import type { StealthChallengeRuntime } from "../runtime/stealth-akamai-sbsd.js";
124
+ import { StealthCookieJar } from "../runtime/stealth-cookies.js";
111
125
  import { createSttClientFromEnv } from "../runtime/stt.js";
112
126
  import {
113
127
  createTraceContext,
@@ -118,21 +132,21 @@ import {
118
132
  } from "../runtime/trace.js";
119
133
  import { resolveTraceConfigFromEnv } from "../runtime/trace-config.js";
120
134
  import { parseSchema } from "../schema.js";
135
+ import { StatefulRoutingDeadlineError } from "../stateful/errors.js";
121
136
  import {
122
137
  STATEFUL_NONCE_HEADER as STATEFUL_FORWARDING_NONCE_HEADER,
123
138
  STATEFUL_SIGNATURE_HEADER as STATEFUL_FORWARDING_SIGNATURE_HEADER,
124
139
  STATEFUL_TIMESTAMP_HEADER as STATEFUL_FORWARDING_TIMESTAMP_HEADER,
125
140
  verifyStatefulRequestSignature,
126
141
  } from "../stateful-signing.js";
127
- import { StatefulRoutingDeadlineError } from "../stateful/errors.js";
128
142
  import { getStealthProfile } from "../stealth/profiles.js";
129
- import { sanitizeTraceAttributes } from "../trace-sanitization.js";
130
143
  import {
131
144
  APIFUSE_STREAM_DONE_EVENT,
132
145
  APIFUSE_STREAM_ERROR_EVENT,
133
146
  encodeSseEvent,
134
147
  error as streamError,
135
148
  } from "../stream.js";
149
+ import { sanitizeSpanNameForOutput, sanitizeTraceAttributes } from "../trace-sanitization.js";
136
150
  import type {
137
151
  AuthContext,
138
152
  AuthTurn,
@@ -140,15 +154,15 @@ import type {
140
154
  FlowContext,
141
155
  FlowContextStore,
142
156
  HttpRetrySummary,
157
+ OcrContext,
143
158
  OperationDefinition,
144
159
  OperationErrorCode,
145
160
  OperationHttpStreamTransport,
146
161
  OperationSseTransport,
147
- OcrContext,
148
- ProviderErrorStatus,
149
- ProviderContext,
150
162
  ProviderCacheResponseMeta,
163
+ ProviderContext,
151
164
  ProviderDefinition,
165
+ ProviderErrorStatus,
152
166
  ProviderFilesContext,
153
167
  ProviderProxyPolicy,
154
168
  ProviderRuntimeState,
@@ -162,6 +176,10 @@ import type {
162
176
  import { VALID_OPERATION_ERROR_STATUSES } from "../types.js";
163
177
  import type { SelfTestCancellationLogEvent } from "./self-test.js";
164
178
  import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
179
+ import {
180
+ collectStaticDiagnosticSensitiveValues,
181
+ createDiagnosticEnvObserver,
182
+ } from "./sensitive-values.js";
165
183
  import { resolveServerTraceContextOptions } from "./trace-output.js";
166
184
  import {
167
185
  type AuthFlowRequest,
@@ -307,9 +325,7 @@ export type ProviderServerOperationExecutorInput<
307
325
 
308
326
  export type ProviderServerOperationExecutor<
309
327
  TContext extends Partial<ProviderContext> = ProviderContext,
310
- > = (
311
- input: ProviderServerOperationExecutorInput<TContext>,
312
- ) => Promise<unknown>;
328
+ > = (input: ProviderServerOperationExecutorInput<TContext>) => Promise<unknown>;
313
329
 
314
330
  type RequestTerminalOutcome =
315
331
  | { kind: "completed"; status: number }
@@ -317,6 +333,7 @@ type RequestTerminalOutcome =
317
333
  | { kind: "cancelled"; status?: number };
318
334
 
319
335
  type RequestStreamLifecycle = {
336
+ redact: DiagnosticRedactor;
320
337
  runStep<T>(fn: () => Promise<T>): Promise<T>;
321
338
  registerCleanup(cleanup: () => void | Promise<void>): void;
322
339
  terminalize(outcome: RequestTerminalOutcome): void;
@@ -823,9 +840,11 @@ function providerSecretNames(provider: ProviderDefinition): string[] {
823
840
  }
824
841
 
825
842
  type RequestScopeContext = {
843
+ redact: DiagnosticRedactor;
826
844
  trace: RuntimeTraceContext;
827
845
  telemetry: RequestTelemetry;
828
846
  resolverTelemetry: ResolverTelemetryCollector;
847
+ nativeTelemetry: NativeTelemetryCollector;
829
848
  };
830
849
 
831
850
  function createProviderContext(
@@ -899,12 +918,10 @@ function createProviderContext(
899
918
  request.requestId,
900
919
  "stealth",
901
920
  error,
921
+ scope.redact,
902
922
  );
903
923
 
904
- const env = createEnvContext([
905
- ...providerSecretNames(provider),
906
- PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
907
- ]);
924
+ const env = createEnvContext([...providerSecretNames(provider)]);
908
925
  const credential = createCredentialContext({
909
926
  allowedKeys: provider.credential?.keys,
910
927
  mode: request.connection?.mode,
@@ -940,7 +957,7 @@ function createProviderContext(
940
957
  provider.runtime === "browser"
941
958
  ? capabilityModules.browser!.createBrowserClient({
942
959
  allowedHosts: provider.allowedHosts,
943
- cdpUrl: process.env.APIFUSE__CDP_POOL__URL,
960
+ cdpUrl: readDiagnosticEnv("APIFUSE__CDP_POOL__URL"),
944
961
  headless: true,
945
962
  requireCdpPool: isProductionProviderBrowserMode(provider),
946
963
  stealth: true,
@@ -951,6 +968,8 @@ function createProviderContext(
951
968
  ? {
952
969
  native: {
953
970
  network: capabilityModules.nativeNetwork!.createNativeNetworkClient({
971
+ telemetry: scope.telemetry.proxy,
972
+ nativeTelemetry: scope.nativeTelemetry,
954
973
  egress: provider.native.network,
955
974
  proxyPolicy: resolveNativeProxyPolicy(provider),
956
975
  affinityKey: proxyClientOptions.affinityKey,
@@ -984,16 +1003,14 @@ function createProviderContext(
984
1003
  createUnsupportedResolverClient("Provider does not declare resolver capability"),
985
1004
  signal,
986
1005
  ),
987
- choice: createProviderChoiceContext({
1006
+ handle: createHandleContext({
988
1007
  providerId: provider.id,
989
- env,
990
1008
  request: requestContext,
991
- credential,
992
1009
  state: requestState,
993
1010
  onTelemetry: (event) =>
994
1011
  (options.logger ?? defaultProviderServerLogger)({
995
1012
  level: "info",
996
- event: "provider_choice_token",
1013
+ event: "provider_handle",
997
1014
  ...event,
998
1015
  }),
999
1016
  }),
@@ -1101,6 +1118,7 @@ function createAuthFlowContext(
1101
1118
  providerId: provider.id,
1102
1119
  flowId: request.flowId,
1103
1120
  affinityKey: proxyClientOptions.affinityKey,
1121
+ onHandle: (handle) => registerDiagnosticSensitiveValues(scope.trace, [handle]),
1104
1122
  ...(request.engine?.egressLease ? { handle: request.engine.egressLease } : {}),
1105
1123
  })
1106
1124
  : undefined;
@@ -1161,6 +1179,7 @@ function createAuthFlowContext(
1161
1179
  request.requestId,
1162
1180
  "stealth",
1163
1181
  error,
1182
+ scope.redact,
1164
1183
  );
1165
1184
  const credential = request.connection
1166
1185
  ? createCredentialContext({
@@ -1191,6 +1210,8 @@ function createAuthFlowContext(
1191
1210
  ? {
1192
1211
  native: {
1193
1212
  network: capabilityModules.nativeNetwork!.createNativeNetworkClient({
1213
+ telemetry: scope.telemetry.proxy,
1214
+ nativeTelemetry: scope.nativeTelemetry,
1194
1215
  egress: provider.native.network,
1195
1216
  proxyPolicy: resolveNativeProxyPolicy(provider),
1196
1217
  affinityKey: proxyClientOptions.affinityKey,
@@ -1296,18 +1317,10 @@ export type ProviderServerLogEvent =
1296
1317
  providerId: string;
1297
1318
  missingSecrets: string[];
1298
1319
  }
1299
- | {
1320
+ | ({
1300
1321
  level: "info";
1301
- event: "provider_choice_token";
1302
- providerId: string;
1303
- purpose: string;
1304
- operation: "parse" | "consume";
1305
- format: "word" | "legacy";
1306
- outcome: "success" | "not-found" | "invalid" | "unsupported" | "error";
1307
- consumeMode: "never" | "on-parse" | "explicit";
1308
- consumed: boolean;
1309
- replay: boolean;
1310
- }
1322
+ event: "provider_handle";
1323
+ } & HandleTelemetryEvent)
1311
1324
  | {
1312
1325
  level: "warn";
1313
1326
  event: "provider_cleanup_failed";
@@ -1418,15 +1431,18 @@ function finishRequestCost(input: {
1418
1431
  };
1419
1432
  }
1420
1433
 
1421
- function zodDetails(error: z.ZodError): Array<{
1434
+ function zodDetails(
1435
+ error: z.ZodError,
1436
+ redact?: DiagnosticRedactor,
1437
+ ): Array<{
1422
1438
  path: string;
1423
1439
  code: string;
1424
1440
  message: string;
1425
1441
  }> {
1426
1442
  return error.issues.map((issue) => ({
1427
- path: issue.path.join("."),
1428
- code: issue.code,
1429
- message: issue.message,
1443
+ path: sanitizeSpanNameForOutput(issue.path.join("."), redact),
1444
+ code: sanitizeSpanNameForOutput(issue.code, redact),
1445
+ message: sanitizeSpanNameForOutput(issue.message, redact),
1430
1446
  }));
1431
1447
  }
1432
1448
 
@@ -1453,6 +1469,7 @@ function toErrorResponse(
1453
1469
  error: unknown,
1454
1470
  requestId: string | undefined,
1455
1471
  observabilityDetails: ErrorObservabilityDetails,
1472
+ redact?: DiagnosticRedactor,
1456
1473
  ): OperationErrorResponse {
1457
1474
  const observability = observabilityDetails;
1458
1475
  const source = publicErrorSource(error, observability.category);
@@ -1491,7 +1508,7 @@ function toErrorResponse(
1491
1508
  ...(requestId ? { requestId } : {}),
1492
1509
  retryable: observability.retryable,
1493
1510
  source,
1494
- details: zodDetails(error),
1511
+ details: zodDetails(error, redact),
1495
1512
  },
1496
1513
  };
1497
1514
  }
@@ -1523,6 +1540,48 @@ function toErrorResponse(
1523
1540
  // narrowing from a ProviderError-typed value would collapse the negative branch
1524
1541
  // to `never`. Narrowing from unknown avoids that while still recognizing errors
1525
1542
  // from a duplicate SDK module instance.
1543
+ function ownErrorCode(error: unknown): string | undefined {
1544
+ if (error === null || (typeof error !== "object" && typeof error !== "function")) {
1545
+ return undefined;
1546
+ }
1547
+ try {
1548
+ const descriptor = Object.getOwnPropertyDescriptor(error, "code");
1549
+ return descriptor && Object.hasOwn(descriptor, "value") && typeof descriptor.value === "string"
1550
+ ? descriptor.value
1551
+ : undefined;
1552
+ } catch {
1553
+ return undefined;
1554
+ }
1555
+ }
1556
+
1557
+ function errorChainHasCode(error: unknown, expected: string): boolean {
1558
+ const seen = new Set<object>();
1559
+ let current = error;
1560
+ for (let depth = 0; depth < 5; depth++) {
1561
+ if (current === null || (typeof current !== "object" && typeof current !== "function")) {
1562
+ return false;
1563
+ }
1564
+ if (seen.has(current)) return false;
1565
+ seen.add(current);
1566
+ let code: string | undefined;
1567
+ try {
1568
+ code = providerErrorCode(current) ?? ownErrorCode(current);
1569
+ } catch {
1570
+ code = ownErrorCode(current);
1571
+ }
1572
+ if (code === expected) return true;
1573
+ let cause: PropertyDescriptor | undefined;
1574
+ try {
1575
+ cause = Object.getOwnPropertyDescriptor(current, "cause");
1576
+ } catch {
1577
+ return false;
1578
+ }
1579
+ if (!cause || !Object.hasOwn(cause, "value")) return false;
1580
+ current = cause.value;
1581
+ }
1582
+ return false;
1583
+ }
1584
+
1526
1585
  function providerObservabilityDetails(
1527
1586
  error: unknown,
1528
1587
  declaredErrorCode?: OperationErrorCode,
@@ -1551,26 +1610,45 @@ function providerObservabilityDetails(
1551
1610
  retryable: providerErrorOption(error, "retryable") ?? declaredRetryable ?? false,
1552
1611
  };
1553
1612
  }
1613
+ // Native proxy resolution throws ProxyResolutionError directly, while HTTP
1614
+ // normalizes it beneath a transport_network_error cause and stealth retains
1615
+ // the original code. Preserve proxy attribution across all three forms.
1616
+ if (errorChainHasCode(error, "PROXY_REQUIRED")) {
1617
+ return {
1618
+ category: "proxy_pool",
1619
+ taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1620
+ retryable: true,
1621
+ };
1622
+ }
1554
1623
  if (!isTransportError(error)) {
1555
1624
  return undefined;
1556
1625
  }
1626
+ const errorCode = providerErrorCode(error);
1557
1627
  const isProxyPoolCode =
1558
- providerErrorCode(error) === PROXY_POOL_EXHAUSTED_CODE ||
1559
- providerErrorCode(error) === PROXY_EDGE_AUTH_REJECTED_CODE ||
1560
- providerErrorCode(error) === "PROXY_ALLOCATION_FAILED";
1628
+ errorCode === PROXY_POOL_EXHAUSTED_CODE ||
1629
+ errorCode === PROXY_EDGE_AUTH_REJECTED_CODE ||
1630
+ errorCode === "PROXY_ALLOCATION_FAILED" ||
1631
+ errorCode === "native_proxy_expired" ||
1632
+ errorCode === "native_proxy_invalid";
1561
1633
  const category =
1562
1634
  providerErrorOption(error, "category") ??
1563
1635
  (isProxyPoolCode
1564
1636
  ? "proxy_pool"
1565
- : providerErrorCode(error) === PROXY_AUTH_IP_DENIED_CODE
1637
+ : errorCode === PROXY_AUTH_IP_DENIED_CODE
1566
1638
  ? "anti_bot_blocked"
1567
- : providerErrorCode(error) === "transport_timeout"
1639
+ : errorCode === "transport_timeout" ||
1640
+ errorCode === "native_connection_timeout" ||
1641
+ errorCode === "native_connection_idle_timeout"
1568
1642
  ? "timeout"
1569
- : providerErrorCode(error) === "transport_network_error"
1643
+ : errorCode === "transport_network_error" ||
1644
+ errorCode === "native_connection_failed" ||
1645
+ errorCode === "native_connection_closed"
1570
1646
  ? "network"
1571
- : error.upstreamStatus
1572
- ? categoryForStatus(error.upstreamStatus)
1573
- : "upstream_http");
1647
+ : errorCode === "native_connection_aborted"
1648
+ ? "client_cancelled"
1649
+ : error.upstreamStatus
1650
+ ? categoryForStatus(error.upstreamStatus)
1651
+ : "upstream_http");
1574
1652
  return {
1575
1653
  category,
1576
1654
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
@@ -1787,14 +1865,17 @@ export type ProviderErrorCauseFrame = {
1787
1865
  const MAX_PROVIDER_ERROR_CAUSE_FRAMES = 5;
1788
1866
  const MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH = 300;
1789
1867
 
1790
- function providerErrorCauseMessage(message: string): string {
1791
- const sanitized = sanitizeDiagnosticText(message);
1868
+ function providerErrorCauseMessage(message: string, redact?: DiagnosticRedactor): string {
1869
+ const sanitized = sanitizeDiagnosticText(redactDiagnosticText(message, redact));
1792
1870
  return sanitized.length > MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH
1793
1871
  ? `${sanitized.slice(0, MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH)}… [truncated]`
1794
1872
  : sanitized;
1795
1873
  }
1796
1874
 
1797
- function providerErrorCauseChain(error: unknown): ProviderErrorCauseFrame[] | undefined {
1875
+ function providerErrorCauseChain(
1876
+ error: unknown,
1877
+ redact?: DiagnosticRedactor,
1878
+ ): ProviderErrorCauseFrame[] | undefined {
1798
1879
  if (!(error instanceof Error) && !isProviderError(error)) return undefined;
1799
1880
 
1800
1881
  const seen = new Set<object>([error]);
@@ -1811,9 +1892,9 @@ function providerErrorCauseChain(error: unknown): ProviderErrorCauseFrame[] | un
1811
1892
  const providerObservability = safeProviderErrorObservability(cause);
1812
1893
  const causeCode = providerErrorCode(cause);
1813
1894
  frames.push({
1814
- errorClass: cause.name,
1815
- ...(causeCode !== undefined ? { code: causeCode } : {}),
1816
- message: providerErrorCauseMessage(message),
1895
+ errorClass: redactDiagnosticText(cause.name, redact),
1896
+ ...(causeCode !== undefined ? { code: redactDiagnosticText(causeCode, redact) } : {}),
1897
+ message: providerErrorCauseMessage(message, redact),
1817
1898
  messageLength: message.length,
1818
1899
  messageFingerprint: createHash("sha256").update(message).digest("hex").slice(0, 12),
1819
1900
  ...(providerObservability ? { providerObservability } : {}),
@@ -1837,6 +1918,7 @@ function logProviderError(
1837
1918
  telemetry: RequestTelemetry | undefined,
1838
1919
  observabilityDetails: ErrorObservabilityDetails,
1839
1920
  correlation: RequestCorrelationIds = {},
1921
+ redact?: DiagnosticRedactor,
1840
1922
  ): void {
1841
1923
  const providerCode = isProviderError(error) ? providerErrorCode(error) : undefined;
1842
1924
  const code = isProviderError(error)
@@ -1848,7 +1930,7 @@ function logProviderError(
1848
1930
  : "internal_error";
1849
1931
  const errorClass = error instanceof Error ? error.name : typeof error;
1850
1932
  const message = error instanceof Error ? error.message : String(error);
1851
- const causeChain = providerErrorCauseChain(error);
1933
+ const causeChain = providerErrorCauseChain(error, redact);
1852
1934
  const details = observabilityDetails;
1853
1935
  const isUnregisteredProviderErrorCode =
1854
1936
  status === 500 &&
@@ -1868,24 +1950,28 @@ function logProviderError(
1868
1950
  emit({
1869
1951
  level: status >= 500 ? "error" : "warn",
1870
1952
  event: "provider_request_failed",
1871
- providerId: provider.id,
1953
+ providerId: redactDiagnosticText(provider.id, redact),
1872
1954
  kind,
1873
- route,
1874
- ...(requestId ? { requestId } : {}),
1955
+ route: redactCriticalDiagnosticText(route, redact),
1956
+ ...(requestId ? { requestId: redactCriticalDiagnosticText(requestId, redact) } : {}),
1875
1957
  ...(correlation.connectionId !== undefined
1876
- ? { connectionId: correlation.connectionId }
1958
+ ? { connectionId: redactDiagnosticText(correlation.connectionId, redact) }
1959
+ : {}),
1960
+ ...(correlation.flowId !== undefined
1961
+ ? { flowId: redactDiagnosticText(correlation.flowId, redact) }
1962
+ : {}),
1963
+ ...(correlation.tenantId !== undefined
1964
+ ? { tenantId: redactDiagnosticText(correlation.tenantId, redact) }
1877
1965
  : {}),
1878
- ...(correlation.flowId !== undefined ? { flowId: correlation.flowId } : {}),
1879
- ...(correlation.tenantId !== undefined ? { tenantId: correlation.tenantId } : {}),
1880
1966
  ...(correlation.requestedProviderId !== undefined
1881
- ? { requestedProviderId: correlation.requestedProviderId }
1967
+ ? { requestedProviderId: redactDiagnosticText(correlation.requestedProviderId, redact) }
1882
1968
  : {}),
1883
1969
  status,
1884
1970
  ...cost,
1885
1971
  ...(telemetryPayload ?? {}),
1886
- code,
1887
- errorClass,
1888
- message,
1972
+ code: sanitizeSpanNameForOutput(redactCriticalDiagnosticText(code, redact)),
1973
+ errorClass: redactDiagnosticText(errorClass, redact),
1974
+ message: sanitizeDiagnosticText(redactDiagnosticText(message, redact)),
1889
1975
  ...(causeChain ? { causeChain } : {}),
1890
1976
  ...(providerObservability ? { providerObservability } : {}),
1891
1977
  ...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
@@ -1899,7 +1985,7 @@ function logProviderError(
1899
1985
  "Declare this code (with status and retryable) in the operation's errorCodes so it serves its intended status instead of 500.",
1900
1986
  }
1901
1987
  : {}),
1902
- ...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
1988
+ ...(error instanceof z.ZodError ? { issues: zodDetails(error, redact) } : {}),
1903
1989
  });
1904
1990
  }
1905
1991
 
@@ -1911,6 +1997,7 @@ function logProviderCleanupError(
1911
1997
  requestId: string | undefined,
1912
1998
  resource: "browser" | "stealth",
1913
1999
  error: unknown,
2000
+ redact?: DiagnosticRedactor,
1914
2001
  ): void {
1915
2002
  const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
1916
2003
  const errorClass = error instanceof Error ? error.name : typeof error;
@@ -1918,13 +2005,13 @@ function logProviderCleanupError(
1918
2005
  emit({
1919
2006
  level: "warn",
1920
2007
  event: "provider_cleanup_failed",
1921
- providerId: provider.id,
2008
+ providerId: redactDiagnosticText(provider.id, redact),
1922
2009
  kind,
1923
- route: operationId,
1924
- ...(requestId ? { requestId } : {}),
2010
+ route: redactCriticalDiagnosticText(operationId, redact),
2011
+ ...(requestId ? { requestId: redactCriticalDiagnosticText(requestId, redact) } : {}),
1925
2012
  resource,
1926
- errorClass,
1927
- message,
2013
+ errorClass: redactDiagnosticText(errorClass, redact),
2014
+ message: sanitizeDiagnosticText(redactDiagnosticText(message, redact)),
1928
2015
  });
1929
2016
  }
1930
2017
 
@@ -1938,23 +2025,28 @@ function logProviderSuccess(
1938
2025
  cost: ProviderRequestCost,
1939
2026
  telemetry?: RequestTelemetry,
1940
2027
  correlation: RequestCorrelationIds = {},
2028
+ redact?: DiagnosticRedactor,
1941
2029
  ): void {
1942
2030
  const telemetryPayload = telemetry?.toLogPayload();
1943
2031
  const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
1944
2032
  emit({
1945
2033
  level: "info",
1946
2034
  event: "provider_request_completed",
1947
- providerId: provider.id,
2035
+ providerId: redactDiagnosticText(provider.id, redact),
1948
2036
  kind,
1949
- route,
1950
- ...(requestId ? { requestId } : {}),
2037
+ route: redactCriticalDiagnosticText(route, redact),
2038
+ ...(requestId ? { requestId: redactCriticalDiagnosticText(requestId, redact) } : {}),
1951
2039
  ...(correlation.connectionId !== undefined
1952
- ? { connectionId: correlation.connectionId }
2040
+ ? { connectionId: redactDiagnosticText(correlation.connectionId, redact) }
2041
+ : {}),
2042
+ ...(correlation.flowId !== undefined
2043
+ ? { flowId: redactDiagnosticText(correlation.flowId, redact) }
2044
+ : {}),
2045
+ ...(correlation.tenantId !== undefined
2046
+ ? { tenantId: redactDiagnosticText(correlation.tenantId, redact) }
1953
2047
  : {}),
1954
- ...(correlation.flowId !== undefined ? { flowId: correlation.flowId } : {}),
1955
- ...(correlation.tenantId !== undefined ? { tenantId: correlation.tenantId } : {}),
1956
2048
  ...(correlation.requestedProviderId !== undefined
1957
- ? { requestedProviderId: correlation.requestedProviderId }
2049
+ ? { requestedProviderId: redactDiagnosticText(correlation.requestedProviderId, redact) }
1958
2050
  : {}),
1959
2051
  status,
1960
2052
  ...cost,
@@ -1968,6 +2060,7 @@ type RequestScopeFinishResult = {
1968
2060
  };
1969
2061
 
1970
2062
  type RequestScope = RequestScopeContext & {
2063
+ seedCredentials(rawBody: unknown, kind: "operation" | "auth" | "stateful"): void;
1971
2064
  enrich(input: {
1972
2065
  route?: string;
1973
2066
  requestId?: string;
@@ -2039,8 +2132,91 @@ function requestTraceId(
2039
2132
  );
2040
2133
  }
2041
2134
 
2135
+ /** Harvest only credential subtrees, iteratively and before schema diagnostics. */
2136
+ function rawRequestCredentials(
2137
+ rawBody: unknown,
2138
+ kind: "operation" | "auth" | "stateful",
2139
+ ): { values: string[]; exhausted: boolean } {
2140
+ const record = (value: unknown): Record<string, unknown> | undefined =>
2141
+ value !== null && typeof value === "object" ? (value as Record<string, unknown>) : undefined;
2142
+ const own = (value: unknown, key: string): unknown => {
2143
+ const object = record(value);
2144
+ return object && Object.hasOwn(object, key) ? object[key] : undefined;
2145
+ };
2146
+ const body = kind === "stateful" ? own(rawBody, "operationRequest") : rawBody;
2147
+ const values: string[] = [];
2148
+ let remainingBytes = 64 * 1024;
2149
+ let exhausted = false;
2150
+ const add = (value: unknown): void => {
2151
+ if (typeof value !== "string") return;
2152
+ const bytes = Buffer.byteLength(value, "utf8");
2153
+ if (bytes > remainingBytes) {
2154
+ exhausted = true;
2155
+ return;
2156
+ }
2157
+ remainingBytes -= bytes;
2158
+ if (value) values.push(value);
2159
+ const trimmed = value.trim();
2160
+ if (trimmed && trimmed !== value) values.push(trimmed);
2161
+ };
2162
+ function* entries(object: Record<string, unknown>) {
2163
+ for (const key in object) {
2164
+ if (Object.hasOwn(object, key)) yield [key, object[key]] as const;
2165
+ }
2166
+ }
2167
+ const visit = (root: unknown): void => {
2168
+ const object = record(root);
2169
+ if (!object) {
2170
+ add(root);
2171
+ return;
2172
+ }
2173
+ // Connection inventories are usually flat. Walk the root directly and only
2174
+ // allocate iterator frames when a nested credential container is encountered.
2175
+ for (const key in object) {
2176
+ if (!Object.hasOwn(object, key)) continue;
2177
+ const value = object[key];
2178
+ add(key);
2179
+ if (exhausted) break;
2180
+ const child = record(value);
2181
+ if (!child) {
2182
+ add(value);
2183
+ if (exhausted) break;
2184
+ continue;
2185
+ }
2186
+ const stack = [{ iterator: entries(child), depth: 1 }];
2187
+ while (stack.length && !exhausted) {
2188
+ const frame = stack[stack.length - 1];
2189
+ const entry = frame.iterator.next();
2190
+ if (entry.done) {
2191
+ stack.pop();
2192
+ continue;
2193
+ }
2194
+ const [key, value] = entry.value;
2195
+ add(key);
2196
+ if (exhausted) break;
2197
+ const child = record(value);
2198
+ if (child) {
2199
+ if (frame.depth >= 8) {
2200
+ exhausted = true;
2201
+ break;
2202
+ }
2203
+ stack.push({ iterator: entries(child), depth: frame.depth + 1 });
2204
+ } else add(value);
2205
+ }
2206
+ if (exhausted) break;
2207
+ }
2208
+ };
2209
+ visit(own(own(body, "connection"), "secrets"));
2210
+ if (kind === "auth" && !exhausted) {
2211
+ add(own(own(body, "engine"), "egressLease"));
2212
+ if (!exhausted) visit(own(body, "input"));
2213
+ }
2214
+ return { values, exhausted };
2215
+ }
2216
+
2042
2217
  function createRequestScope(input: {
2043
2218
  provider: ProviderDefinition;
2219
+ staticSensitiveValues: CompiledDiagnosticSensitiveValues;
2044
2220
  kind: "operation" | "auth";
2045
2221
  route: string;
2046
2222
  requestId?: string;
@@ -2052,8 +2228,25 @@ function createRequestScope(input: {
2052
2228
  setHeader?: (name: string, value: string) => void;
2053
2229
  declaredErrorCode?: (error: unknown) => OperationErrorCode | undefined;
2054
2230
  }): RequestScope {
2231
+ const sensitiveRegistry = createDiagnosticRedactor([], input.staticSensitiveValues);
2232
+ const redact = (text: string) => redactDiagnosticText(text, sensitiveRegistry.redact);
2233
+ bindCriticalDiagnosticRedactor(
2234
+ redact,
2235
+ sensitiveRegistry.redactCritical,
2236
+ sensitiveRegistry.redactStructured,
2237
+ () => sensitiveRegistry.suppressed,
2238
+ );
2055
2239
  const requestCost = startRequestCost();
2056
- const traceConfig = resolveTraceConfigFromEnv();
2240
+ const observeEnv = {
2241
+ observe: createDiagnosticEnvObserver(
2242
+ input.provider,
2243
+ sensitiveRegistry.add,
2244
+ sensitiveRegistry.has,
2245
+ ),
2246
+ finished: false,
2247
+ staticValues: input.staticSensitiveValues,
2248
+ };
2249
+ const traceConfig = withDiagnosticEnv(observeEnv, () => resolveTraceConfigFromEnv());
2057
2250
  const details = {
2058
2251
  route: input.route,
2059
2252
  requestId: input.requestId,
@@ -2072,15 +2265,21 @@ function createRequestScope(input: {
2072
2265
  const initialTraceId = requestTraceId(input.headers, input.requestId);
2073
2266
  const trace: RuntimeTraceContext = traceConfig
2074
2267
  ? createTraceContext({
2075
- ...resolveServerTraceContextOptions(traceConfig, traceAttributes),
2268
+ ...withDiagnosticEnv(observeEnv, () =>
2269
+ resolveServerTraceContextOptions(traceConfig, traceAttributes, undefined, redact),
2270
+ ),
2271
+ redact,
2076
2272
  ...(initialTraceId ? { traceId: initialTraceId } : {}),
2077
2273
  })
2078
- : createTraceContext();
2274
+ : createTraceContext({ redact });
2275
+ bindDiagnosticSensitiveRegistry(trace, sensitiveRegistry);
2079
2276
  const proxyCollector = new ProxyTelemetryCollector();
2080
- const resolverCollector = new ResolverTelemetryCollector();
2277
+ const resolverCollector = new ResolverTelemetryCollector({ redact });
2278
+ const nativeCollector = new NativeTelemetryCollector({ redact });
2081
2279
  const telemetry = new RequestTelemetry(trace);
2082
2280
  telemetry.register(proxyCollector);
2083
2281
  telemetry.register(resolverCollector);
2282
+ telemetry.register(nativeCollector);
2084
2283
  let rootRunner: <T>(fn: () => Promise<T>) => Promise<T> = (fn) => fn();
2085
2284
  let resolveRoot!: (outcome: RequestTerminalOutcome) => void;
2086
2285
  const rootTerminal = new Promise<RequestTerminalOutcome>((resolve) => {
@@ -2104,12 +2303,14 @@ function createRequestScope(input: {
2104
2303
  if (outcome.kind === "cancelled") return clientCancelledError();
2105
2304
  return undefined;
2106
2305
  };
2107
- const root = trace.span(`request:${input.kind}:${input.route}`, async () => {
2108
- rootRunner = AsyncLocalStorage.snapshot();
2109
- const outcome = await rootTerminal;
2110
- const error = terminalError(outcome);
2111
- if (error !== undefined) throw error;
2112
- });
2306
+ const root = withDiagnosticEnv(observeEnv, () =>
2307
+ trace.span(`request:${input.kind}:${input.route}`, async () => {
2308
+ rootRunner = AsyncLocalStorage.snapshot();
2309
+ const outcome = await rootTerminal;
2310
+ const error = terminalError(outcome);
2311
+ if (error !== undefined) throw error;
2312
+ }),
2313
+ );
2113
2314
  void root.catch(() => undefined);
2114
2315
 
2115
2316
  const settleRoot = (): void => {
@@ -2144,7 +2345,7 @@ function createRequestScope(input: {
2144
2345
  traceAttributes.route = details.route;
2145
2346
  const traceId = requestTraceId(details.headers, details.requestId);
2146
2347
  const sanitizedAttributes = Object.fromEntries(
2147
- Object.entries(sanitizeTraceAttributes(traceAttributes)).map(([key, value]) => [
2348
+ Object.entries(sanitizeTraceAttributes(traceAttributes, redact)).map(([key, value]) => [
2148
2349
  key,
2149
2350
  String(value),
2150
2351
  ]),
@@ -2156,9 +2357,17 @@ function createRequestScope(input: {
2156
2357
  };
2157
2358
 
2158
2359
  const scope: RequestScope = {
2360
+ redact,
2159
2361
  trace,
2160
2362
  telemetry,
2161
2363
  resolverTelemetry: resolverCollector,
2364
+ nativeTelemetry: nativeCollector,
2365
+ seedCredentials(rawBody, kind): void {
2366
+ const harvested = rawRequestCredentials(rawBody, kind);
2367
+ sensitiveRegistry.add(harvested.values);
2368
+ // An incomplete inventory cannot certify free text: emit less.
2369
+ if (harvested.exhausted) sensitiveRegistry.suppress();
2370
+ },
2162
2371
  enrich(enrichment): void {
2163
2372
  if (terminalOutcome) return;
2164
2373
  if (enrichment.route !== undefined) details.route = enrichment.route;
@@ -2208,6 +2417,7 @@ function createRequestScope(input: {
2208
2417
  },
2209
2418
  terminalize(outcome): RequestScopeFinishResult {
2210
2419
  if (finishedResult) return finishedResult;
2420
+ observeEnv.finished = true;
2211
2421
  terminalOutcome = outcome;
2212
2422
  const error = terminalError(outcome);
2213
2423
  finishedResult = {};
@@ -2215,8 +2425,7 @@ function createRequestScope(input: {
2215
2425
  const declaredErrorCode =
2216
2426
  error === undefined ? undefined : input.declaredErrorCode?.(error);
2217
2427
  const status =
2218
- outcome.status ??
2219
- (error === undefined ? 200 : toStatusCode(error, declaredErrorCode));
2428
+ outcome.status ?? (error === undefined ? 200 : toStatusCode(error, declaredErrorCode));
2220
2429
  finishedResult = headerSnapshot(error);
2221
2430
  const cost = finishRequestCost(requestCost);
2222
2431
  try {
@@ -2231,6 +2440,7 @@ function createRequestScope(input: {
2231
2440
  cost,
2232
2441
  telemetry,
2233
2442
  details.correlation,
2443
+ redact,
2234
2444
  );
2235
2445
  } else {
2236
2446
  logProviderError(
@@ -2246,6 +2456,7 @@ function createRequestScope(input: {
2246
2456
  telemetry,
2247
2457
  finishedResult.errorObservability as ErrorObservabilityDetails,
2248
2458
  details.correlation,
2459
+ redact,
2249
2460
  );
2250
2461
  }
2251
2462
  } catch {
@@ -2281,8 +2492,9 @@ function createRequestScope(input: {
2281
2492
  return scope;
2282
2493
  }
2283
2494
 
2284
- // A raw `Response` result skips `toErrorResponse`, so its body stays
2285
- // provider-owned, but the observability header is still SDK-owned: derive it
2495
+ // A raw `Response` result skips `toErrorResponse`. Its payload shape stays
2496
+ // provider-owned; known credentials in error payloads still need redaction.
2497
+ // The observability header is SDK-owned: derive it
2286
2498
  // from the status. Only timeouts, rate limits, and 5xx are retryable;
2287
2499
  // `isRetryableCategory` would otherwise mark every other 4xx (`upstream_http`)
2288
2500
  // as retryable.
@@ -2297,11 +2509,12 @@ function rawResponseErrorObservability(status: number): ErrorObservabilityDetail
2297
2509
  // `rawStatusFallback` is decided by the caller from the outcome, not from a
2298
2510
  // missing snapshot: a failed outcome whose thrown value is `undefined` also has
2299
2511
  // no `errorObservability`, and its 500 carries the SDK envelope, not a raw body.
2300
- function responseWithRequestScopeHeaders(
2512
+ async function responseWithRequestScopeHeaders(
2301
2513
  response: Response,
2302
2514
  finished: RequestScopeFinishResult,
2303
2515
  rawStatusFallback: boolean,
2304
- ): Response {
2516
+ redact: DiagnosticRedactor,
2517
+ ): Promise<Response> {
2305
2518
  const headers = new Headers(response.headers);
2306
2519
  headers.delete(PROVIDER_TELEMETRY_HEADER);
2307
2520
  if (finished.providerTelemetryHeader) {
@@ -2316,24 +2529,36 @@ function responseWithRequestScopeHeaders(
2316
2529
  if (errorObservability) {
2317
2530
  headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservability));
2318
2531
  }
2319
- return new Response(response.body, {
2532
+ // Raw Response bodies are provider-owned. Preserve their stream and representation.
2533
+ const body = response.body;
2534
+ const statusText =
2535
+ rawStatusFallback && response.status >= 400
2536
+ ? redactDiagnosticText(response.statusText, redact)
2537
+ : response.statusText;
2538
+ return new Response(body, {
2320
2539
  headers,
2321
2540
  status: response.status,
2322
- statusText: response.statusText,
2541
+ // When free text cannot be certified, use the SDK's standard HTTP reason
2542
+ // phrase. It preserves the status contract without trusting provider text.
2543
+ statusText:
2544
+ statusText === REDACTION_FAILED && isDiagnosticRedactionSuppressed(redact)
2545
+ ? (STATUS_CODES[response.status] ?? "")
2546
+ : statusText,
2323
2547
  });
2324
2548
  }
2325
2549
 
2326
- function finalizeRequestResponse(
2550
+ async function finalizeRequestResponse(
2327
2551
  scope: RequestScope,
2328
2552
  response: Response,
2329
2553
  outcome: RequestTerminalOutcome,
2330
- ): Response {
2554
+ ): Promise<Response> {
2331
2555
  try {
2332
2556
  const error = outcome.kind === "failed" ? outcome.error : undefined;
2333
- const finalResponse = responseWithRequestScopeHeaders(
2557
+ const finalResponse = await responseWithRequestScopeHeaders(
2334
2558
  response,
2335
2559
  scope.snapshotHeaders(error),
2336
2560
  outcome.kind === "completed",
2561
+ scope.redact,
2337
2562
  );
2338
2563
  scope.terminalize(outcome);
2339
2564
  return finalResponse;
@@ -2552,11 +2777,7 @@ function toSseResponse(
2552
2777
  const validated = await validateSseEvent(operation, next.value);
2553
2778
  const encodedEvent = encodeSseEvent(validated);
2554
2779
  const bytes = encoder.encode(encodedEvent);
2555
- assertStreamPayloadWithinLimit(
2556
- bytes.byteLength,
2557
- transport?.maxEventBytes,
2558
- "event",
2559
- );
2780
+ assertStreamPayloadWithinLimit(bytes.byteLength, transport?.maxEventBytes, "event");
2560
2781
  return bytes;
2561
2782
  });
2562
2783
  controller.enqueue(encodedBytes);
@@ -2565,9 +2786,13 @@ function toSseResponse(
2565
2786
  controller.enqueue(
2566
2787
  encoder.encode(
2567
2788
  encodeSseEvent(
2568
- streamError("stream_error", message, {
2569
- ...(requestId ? { requestId } : {}),
2570
- }),
2789
+ streamError(
2790
+ "stream_error",
2791
+ sanitizeDiagnosticText(redactDiagnosticText(message, lifecycle.redact)),
2792
+ {
2793
+ ...(requestId ? { requestId } : {}),
2794
+ },
2795
+ ),
2571
2796
  ),
2572
2797
  ),
2573
2798
  );
@@ -2810,6 +3035,7 @@ async function handleOperation(
2810
3035
  request.requestId,
2811
3036
  "stealth",
2812
3037
  error,
3038
+ scope.redact,
2813
3039
  );
2814
3040
  }
2815
3041
  }
@@ -2825,11 +3051,13 @@ async function handleOperation(
2825
3051
  request.requestId,
2826
3052
  "browser",
2827
3053
  error,
3054
+ scope.redact,
2828
3055
  );
2829
3056
  }
2830
3057
  }
2831
3058
  };
2832
3059
  const streamLifecycle: RequestStreamLifecycle = {
3060
+ redact: scope.redact,
2833
3061
  runStep<T>(fn: () => Promise<T>): Promise<T> {
2834
3062
  return scope.runStreamStep(fn);
2835
3063
  },
@@ -2953,6 +3181,7 @@ async function handleAuthFlow(
2953
3181
  request.requestId,
2954
3182
  "stealth",
2955
3183
  error,
3184
+ scope.redact,
2956
3185
  );
2957
3186
  }
2958
3187
  }
@@ -3074,12 +3303,15 @@ function operationRequestFromForwardingEnvelope(
3074
3303
  };
3075
3304
  }
3076
3305
 
3077
- function parseStatefulForwardingEnvelope(rawBody: unknown): ProviderServerStatefulForwardEnvelope {
3306
+ function parseStatefulForwardingEnvelope(
3307
+ rawBody: unknown,
3308
+ redact: DiagnosticRedactor,
3309
+ ): ProviderServerStatefulForwardEnvelope {
3078
3310
  const parsed = ProviderServerStatefulForwardEnvelopeSchema.safeParse(rawBody);
3079
3311
  if (parsed.success) return parsed.data;
3080
3312
  throw new ProviderError("Stateful forwarding envelope is invalid.", {
3081
3313
  code: "STATEFUL_FORWARDING_ENVELOPE_INVALID",
3082
- details: zodDetails(parsed.error),
3314
+ details: zodDetails(parsed.error, redact),
3083
3315
  });
3084
3316
  }
3085
3317
 
@@ -3087,7 +3319,9 @@ function parseStatefulForwardingEnvelope(rawBody: unknown): ProviderServerStatef
3087
3319
  * Primary, cross-runtime app factory. Declared capability ESM is preloaded
3088
3320
  * asynchronously, so this path works on Bun and every supported Node release.
3089
3321
  */
3090
- export async function createServerAppAsync<TContext extends Partial<ProviderContext> = ProviderContext>(
3322
+ export async function createServerAppAsync<
3323
+ TContext extends Partial<ProviderContext> = ProviderContext,
3324
+ >(
3091
3325
  provider: ProviderDefinition<TContext>,
3092
3326
  options: ProviderServerOptions<TContext> = {},
3093
3327
  ): Promise<Hono> {
@@ -3134,6 +3368,12 @@ function createServerAppWithCapabilityModules(
3134
3368
  engine: serverOptions.engine ?? createInProcessProviderEngine(),
3135
3369
  };
3136
3370
  const app = new Hono();
3371
+ // Compile the startup inventory once, composed with the shared outside-context fallback.
3372
+ const staticSensitiveValues = compileProcessDiagnosticSensitiveValues(
3373
+ collectStaticDiagnosticSensitiveValues(provider, {
3374
+ statefulForwardingSecret: options.statefulForwarding?.secret,
3375
+ }),
3376
+ );
3137
3377
  const logger = options.logger ?? defaultProviderServerLogger;
3138
3378
  const operationErrorCodes = buildOperationErrorCodeLookup(provider);
3139
3379
  const statefulForwardingReplayCache = new StatefulForwardingReplayCache(
@@ -3189,6 +3429,7 @@ function createServerAppWithCapabilityModules(
3189
3429
  const operation = "stateful-internal";
3190
3430
  const requestScope = createRequestScope({
3191
3431
  provider,
3432
+ staticSensitiveValues,
3192
3433
  kind: "operation",
3193
3434
  route: operation,
3194
3435
  headers: Object.fromEntries(c.req.raw.headers.entries()),
@@ -3218,7 +3459,8 @@ function createServerAppWithCapabilityModules(
3218
3459
  code: "STATEFUL_FORWARDING_ENVELOPE_INVALID",
3219
3460
  });
3220
3461
  }
3221
- const envelope = parseStatefulForwardingEnvelope(rawBody);
3462
+ requestScope.seedCredentials(rawBody, "stateful");
3463
+ const envelope = parseStatefulForwardingEnvelope(rawBody, requestScope.redact);
3222
3464
  if (envelope.providerId !== provider.id) {
3223
3465
  throw new ProviderError(
3224
3466
  "Stateful forwarding envelope providerId does not match the served provider.",
@@ -3354,7 +3596,10 @@ function createServerAppWithCapabilityModules(
3354
3596
  ...(requestId ? { requestId } : {}),
3355
3597
  });
3356
3598
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
3357
- const response = c.json(toErrorResponse(error, requestId, observabilityDetails), status);
3599
+ const response = c.json(
3600
+ toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
3601
+ status,
3602
+ );
3358
3603
  return finalizeRequestResponse(requestScope, response, {
3359
3604
  kind: "failed",
3360
3605
  status,
@@ -3368,6 +3613,7 @@ function createServerAppWithCapabilityModules(
3368
3613
  const operation = c.req.param("operation");
3369
3614
  const requestScope = createRequestScope({
3370
3615
  provider,
3616
+ staticSensitiveValues,
3371
3617
  kind: "operation",
3372
3618
  route: operation,
3373
3619
  operationId: operation,
@@ -3381,6 +3627,7 @@ function createServerAppWithCapabilityModules(
3381
3627
  .clone()
3382
3628
  .json()
3383
3629
  .catch(() => undefined);
3630
+ requestScope.seedCredentials(rawBody, "operation");
3384
3631
  const body = OperationRequestSchema.parse(rawBody);
3385
3632
  const requestHeaders = Object.fromEntries(c.req.raw.headers.entries());
3386
3633
  body.headers = { ...requestHeaders, ...body.headers };
@@ -3405,7 +3652,12 @@ function createServerAppWithCapabilityModules(
3405
3652
  );
3406
3653
  const response = handled instanceof Response ? handled : c.json(handled);
3407
3654
  return streaming
3408
- ? responseWithRequestScopeHeaders(response, requestScope.snapshotHeaders(), true)
3655
+ ? responseWithRequestScopeHeaders(
3656
+ response,
3657
+ requestScope.snapshotHeaders(),
3658
+ true,
3659
+ requestScope.redact,
3660
+ )
3409
3661
  : response;
3410
3662
  };
3411
3663
  const response = streaming
@@ -3422,7 +3674,10 @@ function createServerAppWithCapabilityModules(
3422
3674
  const requestId = extractRequestId(rawBody);
3423
3675
  requestScope.enrich({ ...(requestId ? { requestId } : {}) });
3424
3676
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
3425
- const response = c.json(toErrorResponse(error, requestId, observabilityDetails), status);
3677
+ const response = c.json(
3678
+ toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
3679
+ status,
3680
+ );
3426
3681
  return finalizeRequestResponse(requestScope, response, {
3427
3682
  kind: "failed",
3428
3683
  status,
@@ -3448,6 +3703,7 @@ function createServerAppWithCapabilityModules(
3448
3703
  let rawBody: unknown;
3449
3704
  const requestScope = createRequestScope({
3450
3705
  provider,
3706
+ staticSensitiveValues,
3451
3707
  kind: "auth",
3452
3708
  route: logRoute,
3453
3709
  headers: Object.fromEntries(c.req.raw.headers.entries()),
@@ -3459,6 +3715,7 @@ function createServerAppWithCapabilityModules(
3459
3715
  .clone()
3460
3716
  .json()
3461
3717
  .catch(() => undefined);
3718
+ requestScope.seedCredentials(rawBody, "auth");
3462
3719
  const body = withAuthRequestHeaders(
3463
3720
  AuthFlowRequestSchema.parse(rawBody),
3464
3721
  c.req.raw.headers,
@@ -3498,7 +3755,10 @@ function createServerAppWithCapabilityModules(
3498
3755
  const requestId = extractRequestId(rawBody);
3499
3756
  requestScope.enrich({ ...(requestId ? { requestId } : {}) });
3500
3757
  const observabilityDetails = errorObservabilityDetails(error);
3501
- const response = c.json(toErrorResponse(error, requestId, observabilityDetails), status);
3758
+ const response = c.json(
3759
+ toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
3760
+ status,
3761
+ );
3502
3762
  return finalizeRequestResponse(requestScope, response, {
3503
3763
  kind: "failed",
3504
3764
  status,