@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.
- package/AUTHORING.md +33 -16
- package/CHANGELOG.md +8 -0
- package/bin/apifuse-dev.ts +7 -8
- package/bin/apifuse-pack-types.ts +22 -0
- package/bin/apifuse-perf.ts +45 -10
- package/bin/apifuse-record.ts +6 -8
- package/bin/apifuse-submit-check.ts +2 -2
- package/dist/config/loader.js +13 -12
- package/dist/define.d.ts +3 -2
- package/dist/define.js +1 -1
- package/dist/dev.js +3 -1
- package/dist/engine.d.ts +7 -6
- package/dist/engine.js +6 -6
- package/dist/error-resolution.js +21 -4
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +9 -0
- package/dist/handle-meta.d.ts +39 -0
- package/dist/handle-meta.js +29 -0
- package/dist/handle.d.ts +187 -0
- package/dist/handle.js +254 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -3
- package/dist/lint.d.ts +3 -0
- package/dist/lint.js +388 -62
- package/dist/provider.d.ts +4 -4
- package/dist/provider.js +3 -3
- package/dist/runtime/browser.js +2 -1
- package/dist/runtime/cache.js +3 -2
- package/dist/runtime/diagnostic-env-values.d.ts +5 -0
- package/dist/runtime/diagnostic-env-values.js +57 -0
- package/dist/runtime/diagnostic-env.d.ts +11 -0
- package/dist/runtime/diagnostic-env.js +35 -0
- package/dist/runtime/diagnostic-redactor.d.ts +49 -0
- package/dist/runtime/diagnostic-redactor.js +569 -0
- package/dist/runtime/egress-lease.d.ts +2 -0
- package/dist/runtime/egress-lease.js +4 -1
- package/dist/runtime/env.js +2 -1
- package/dist/runtime/handle-wordlist.d.ts +43 -0
- package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
- package/dist/runtime/handle.d.ts +71 -0
- package/dist/runtime/handle.js +895 -0
- package/dist/runtime/instrumentation.js +3 -0
- package/dist/runtime/native-network.d.ts +10 -1
- package/dist/runtime/native-network.js +230 -29
- package/dist/runtime/native-telemetry.d.ts +142 -0
- package/dist/runtime/native-telemetry.js +231 -0
- package/dist/runtime/ocr.js +2 -1
- package/dist/runtime/otlp.js +5 -4
- package/dist/runtime/proxy-nodemaven.js +3 -2
- package/dist/runtime/request-options.d.ts +7 -0
- package/dist/runtime/request-options.js +62 -22
- package/dist/runtime/resolver-vendors/browser.js +0 -1
- package/dist/runtime/resolver.js +3 -2
- package/dist/runtime/state.d.ts +4 -1
- package/dist/runtime/state.js +32 -17
- package/dist/runtime/stt.js +3 -2
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +115 -20
- package/dist/schema.d.ts +11 -0
- package/dist/schema.js +56 -2
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/self-test-redaction.js +8 -33
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +128 -0
- package/dist/server/serve-implementation.d.ts +4 -11
- package/dist/server/serve-implementation.js +305 -96
- package/dist/server/trace-output.d.ts +2 -1
- package/dist/server/trace-output.js +46 -14
- package/dist/testing/run.js +10 -7
- package/dist/trace-sanitization.d.ts +4 -3
- package/dist/trace-sanitization.js +45 -22
- package/dist/types.d.ts +4 -107
- package/package.json +2 -1
- package/src/config/loader.ts +13 -12
- package/src/define.ts +4 -3
- package/src/dev.ts +3 -1
- package/src/engine.ts +8 -8
- package/src/error-resolution.ts +21 -4
- package/src/errors.ts +13 -0
- package/src/handle-meta.ts +58 -0
- package/src/handle.ts +529 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/index.ts +28 -13
- package/src/lint.ts +456 -71
- package/src/provider.ts +10 -20
- package/src/runtime/browser.ts +2 -1
- package/src/runtime/cache.ts +3 -2
- package/src/runtime/diagnostic-env-values.ts +56 -0
- package/src/runtime/diagnostic-env.ts +55 -0
- package/src/runtime/diagnostic-redactor.ts +662 -0
- package/src/runtime/egress-lease.ts +6 -1
- package/src/runtime/env.ts +2 -1
- package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
- package/src/runtime/handle.ts +1162 -0
- package/src/runtime/instrumentation.ts +3 -0
- package/src/runtime/native-network.ts +281 -33
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/ocr.ts +2 -1
- package/src/runtime/otlp.ts +5 -4
- package/src/runtime/proxy-nodemaven.ts +3 -2
- package/src/runtime/request-options.ts +76 -28
- package/src/runtime/resolver-vendors/browser.ts +0 -1
- package/src/runtime/resolver.ts +3 -2
- package/src/runtime/state.ts +44 -16
- package/src/runtime/stt.ts +3 -2
- package/src/runtime/trace.ts +168 -26
- package/src/schema.ts +63 -2
- package/src/server/index.ts +17 -0
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/sensitive-values.ts +197 -0
- package/src/server/serve-implementation.ts +388 -128
- package/src/server/trace-output.ts +77 -17
- package/src/testing/run.ts +11 -7
- package/src/trace-sanitization.ts +70 -23
- package/src/types.ts +4 -137
- package/dist/choice-token.d.ts +0 -24
- package/dist/choice-token.js +0 -74
- package/dist/runtime/choice-wordlist.d.ts +0 -9
- package/dist/runtime/choice.d.ts +0 -27
- package/dist/runtime/choice.js +0 -801
- package/src/choice-token.ts +0 -165
- package/src/runtime/choice.ts +0 -1197
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
@@ -7,29 +10,30 @@ import { Hono } from "hono";
|
|
|
7
10
|
import { z } from "zod";
|
|
8
11
|
import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
|
|
9
12
|
import { validateFailClosedDeclaration } from "../declaration-validation.js";
|
|
10
|
-
import { safeProviderErrorObservability } from "../error-observability.js";
|
|
11
13
|
import { createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, isEngineOwnedEnvName, readEngineProxyCredentials, } from "../engine.js";
|
|
14
|
+
import { safeProviderErrorObservability } from "../error-observability.js";
|
|
12
15
|
import { SDK_OWNED_PROVIDER_ERROR_CODES, SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "../error-resolution.js";
|
|
13
16
|
import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, SDKError, } from "../errors.js";
|
|
14
17
|
import { sanitizeDiagnosticText } from "../fixture-sanitization.js";
|
|
15
18
|
import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog.js";
|
|
16
|
-
import { categoryForStatus,
|
|
19
|
+
import { categoryForStatus, isRetryableCategory, PROVIDER_OBSERVABILITY_TAXONOMY_VERSION, sourceForCategory, } from "../observability.js";
|
|
17
20
|
import { createScratchpad } from "../runtime/auth-flow.js";
|
|
18
21
|
import { createProviderCache } from "../runtime/cache.js";
|
|
19
|
-
import {
|
|
22
|
+
import { createHandleContext } from "../runtime/handle.js";
|
|
20
23
|
import { createCredentialContext } from "../runtime/credential.js";
|
|
24
|
+
import { bindDiagnosticSensitiveRegistry, compileProcessDiagnosticSensitiveValues, createDiagnosticRedactor, REDACTION_FAILED, isDiagnosticRedactionSuppressed, bindCriticalDiagnosticRedactor, redactCriticalDiagnosticText, redactDiagnosticText, registerDiagnosticSensitiveValues, } from "../runtime/diagnostic-redactor.js";
|
|
21
25
|
import { createCeremonyEgressLeaseRuntime, ENGINE_CEREMONY_EGRESS_LEASE, } from "../runtime/egress-lease.js";
|
|
22
26
|
import { createEnvContext } from "../runtime/env.js";
|
|
23
27
|
import { executeOperation } from "../runtime/executor.js";
|
|
24
28
|
import { createHttpClient } from "../runtime/http.js";
|
|
25
29
|
import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
|
|
26
|
-
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
27
30
|
import { createOcrClientFromEnv } from "../runtime/ocr.js";
|
|
31
|
+
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
28
32
|
import { PROXY_AUTH_IP_DENIED_CODE, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_EXHAUSTED_CODE, } from "../runtime/proxy-errors.js";
|
|
29
33
|
import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector, } from "../runtime/proxy-telemetry.js";
|
|
30
34
|
import { closedEnum, RequestTelemetry, tenantOpaqueKeys, } from "../runtime/request-telemetry.js";
|
|
35
|
+
import { bindResolverTelemetry, createUnsupportedResolverClient, inheritResolverTelemetryBinding, } from "../runtime/resolver-shared.js";
|
|
31
36
|
import { ResolverTelemetryCollector, } from "../runtime/resolver-telemetry.js";
|
|
32
|
-
import { createUnsupportedResolverClient, bindResolverTelemetry, inheritResolverTelemetryBinding, } from "../runtime/resolver-shared.js";
|
|
33
37
|
import { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRET_CODE, } from "../runtime/secrets.js";
|
|
34
38
|
import { createProviderRuntimeStateFromEnv, createUnsupportedProviderRuntimeState, } from "../runtime/state.js";
|
|
35
39
|
import { StealthCookieJar } from "../runtime/stealth-cookies.js";
|
|
@@ -37,13 +41,14 @@ import { createSttClientFromEnv } from "../runtime/stt.js";
|
|
|
37
41
|
import { createTraceContext, getTraceRecorder, updateTraceContextExportMetadata, } from "../runtime/trace.js";
|
|
38
42
|
import { resolveTraceConfigFromEnv } from "../runtime/trace-config.js";
|
|
39
43
|
import { parseSchema } from "../schema.js";
|
|
40
|
-
import { STATEFUL_NONCE_HEADER as STATEFUL_FORWARDING_NONCE_HEADER, STATEFUL_SIGNATURE_HEADER as STATEFUL_FORWARDING_SIGNATURE_HEADER, STATEFUL_TIMESTAMP_HEADER as STATEFUL_FORWARDING_TIMESTAMP_HEADER, verifyStatefulRequestSignature, } from "../stateful-signing.js";
|
|
41
44
|
import { StatefulRoutingDeadlineError } from "../stateful/errors.js";
|
|
45
|
+
import { STATEFUL_NONCE_HEADER as STATEFUL_FORWARDING_NONCE_HEADER, STATEFUL_SIGNATURE_HEADER as STATEFUL_FORWARDING_SIGNATURE_HEADER, STATEFUL_TIMESTAMP_HEADER as STATEFUL_FORWARDING_TIMESTAMP_HEADER, verifyStatefulRequestSignature, } from "../stateful-signing.js";
|
|
42
46
|
import { getStealthProfile } from "../stealth/profiles.js";
|
|
43
|
-
import { sanitizeTraceAttributes } from "../trace-sanitization.js";
|
|
44
47
|
import { APIFUSE_STREAM_DONE_EVENT, APIFUSE_STREAM_ERROR_EVENT, encodeSseEvent, error as streamError, } from "../stream.js";
|
|
48
|
+
import { sanitizeSpanNameForOutput, sanitizeTraceAttributes } from "../trace-sanitization.js";
|
|
45
49
|
import { VALID_OPERATION_ERROR_STATUSES } from "../types.js";
|
|
46
50
|
import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
|
|
51
|
+
import { collectStaticDiagnosticSensitiveValues, createDiagnosticEnvObserver, } from "./sensitive-values.js";
|
|
47
52
|
import { resolveServerTraceContextOptions } from "./trace-output.js";
|
|
48
53
|
import { AuthFlowRequestSchema, OperationConnectionSchema, OperationRequestSchema, } from "./types.js";
|
|
49
54
|
const DEFAULT_HOST = "0.0.0.0";
|
|
@@ -520,11 +525,8 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
520
525
|
}
|
|
521
526
|
: {}),
|
|
522
527
|
};
|
|
523
|
-
const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error);
|
|
524
|
-
const env = createEnvContext([
|
|
525
|
-
...providerSecretNames(provider),
|
|
526
|
-
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
527
|
-
]);
|
|
528
|
+
const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error, scope.redact);
|
|
529
|
+
const env = createEnvContext([...providerSecretNames(provider)]);
|
|
528
530
|
const credential = createCredentialContext({
|
|
529
531
|
allowedKeys: provider.credential?.keys,
|
|
530
532
|
mode: request.connection?.mode,
|
|
@@ -560,7 +562,7 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
560
562
|
browser: provider.runtime === "browser"
|
|
561
563
|
? capabilityModules.browser.createBrowserClient({
|
|
562
564
|
allowedHosts: provider.allowedHosts,
|
|
563
|
-
cdpUrl:
|
|
565
|
+
cdpUrl: readDiagnosticEnv("APIFUSE__CDP_POOL__URL"),
|
|
564
566
|
headless: true,
|
|
565
567
|
requireCdpPool: isProductionProviderBrowserMode(provider),
|
|
566
568
|
stealth: true,
|
|
@@ -571,6 +573,8 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
571
573
|
? {
|
|
572
574
|
native: {
|
|
573
575
|
network: capabilityModules.nativeNetwork.createNativeNetworkClient({
|
|
576
|
+
telemetry: scope.telemetry.proxy,
|
|
577
|
+
nativeTelemetry: scope.nativeTelemetry,
|
|
574
578
|
egress: provider.native.network,
|
|
575
579
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
576
580
|
affinityKey: proxyClientOptions.affinityKey,
|
|
@@ -592,15 +596,13 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
592
596
|
? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
|
|
593
597
|
: undefined) ??
|
|
594
598
|
createUnsupportedResolverClient("Provider does not declare resolver capability"), signal),
|
|
595
|
-
|
|
599
|
+
handle: createHandleContext({
|
|
596
600
|
providerId: provider.id,
|
|
597
|
-
env,
|
|
598
601
|
request: requestContext,
|
|
599
|
-
credential,
|
|
600
602
|
state: requestState,
|
|
601
603
|
onTelemetry: (event) => (options.logger ?? defaultProviderServerLogger)({
|
|
602
604
|
level: "info",
|
|
603
|
-
event: "
|
|
605
|
+
event: "provider_handle",
|
|
604
606
|
...event,
|
|
605
607
|
}),
|
|
606
608
|
}),
|
|
@@ -668,6 +670,7 @@ function createAuthFlowContext(provider, request, route, options, state, scope,
|
|
|
668
670
|
providerId: provider.id,
|
|
669
671
|
flowId: request.flowId,
|
|
670
672
|
affinityKey: proxyClientOptions.affinityKey,
|
|
673
|
+
onHandle: (handle) => registerDiagnosticSensitiveValues(scope.trace, [handle]),
|
|
671
674
|
...(request.engine?.egressLease ? { handle: request.engine.egressLease } : {}),
|
|
672
675
|
})
|
|
673
676
|
: undefined;
|
|
@@ -697,7 +700,7 @@ function createAuthFlowContext(provider, request, route, options, state, scope,
|
|
|
697
700
|
}
|
|
698
701
|
: {}),
|
|
699
702
|
};
|
|
700
|
-
const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "auth", "flow", request.requestId, "stealth", error);
|
|
703
|
+
const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "auth", "flow", request.requestId, "stealth", error, scope.redact);
|
|
701
704
|
const credential = request.connection
|
|
702
705
|
? createCredentialContext({
|
|
703
706
|
allowedKeys: provider.credential?.keys,
|
|
@@ -727,6 +730,8 @@ function createAuthFlowContext(provider, request, route, options, state, scope,
|
|
|
727
730
|
? {
|
|
728
731
|
native: {
|
|
729
732
|
network: capabilityModules.nativeNetwork.createNativeNetworkClient({
|
|
733
|
+
telemetry: scope.telemetry.proxy,
|
|
734
|
+
nativeTelemetry: scope.nativeTelemetry,
|
|
730
735
|
egress: provider.native.network,
|
|
731
736
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
732
737
|
affinityKey: proxyClientOptions.affinityKey,
|
|
@@ -786,11 +791,11 @@ function finishRequestCost(input) {
|
|
|
786
791
|
cpuTotalMicros: Math.max(0, cpuDelta.user + cpuDelta.system),
|
|
787
792
|
};
|
|
788
793
|
}
|
|
789
|
-
function zodDetails(error) {
|
|
794
|
+
function zodDetails(error, redact) {
|
|
790
795
|
return error.issues.map((issue) => ({
|
|
791
|
-
path: issue.path.join("."),
|
|
792
|
-
code: issue.code,
|
|
793
|
-
message: issue.message,
|
|
796
|
+
path: sanitizeSpanNameForOutput(issue.path.join("."), redact),
|
|
797
|
+
code: sanitizeSpanNameForOutput(issue.code, redact),
|
|
798
|
+
message: sanitizeSpanNameForOutput(issue.message, redact),
|
|
794
799
|
}));
|
|
795
800
|
}
|
|
796
801
|
// Category-level projection with code-aware honesty overrides: a missing
|
|
@@ -813,7 +818,7 @@ function publicErrorSource(error, category) {
|
|
|
813
818
|
}
|
|
814
819
|
return sourceForCategory(category);
|
|
815
820
|
}
|
|
816
|
-
function toErrorResponse(error, requestId, observabilityDetails) {
|
|
821
|
+
function toErrorResponse(error, requestId, observabilityDetails, redact) {
|
|
817
822
|
const observability = observabilityDetails;
|
|
818
823
|
const source = publicErrorSource(error, observability.category);
|
|
819
824
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
@@ -849,7 +854,7 @@ function toErrorResponse(error, requestId, observabilityDetails) {
|
|
|
849
854
|
...(requestId ? { requestId } : {}),
|
|
850
855
|
retryable: observability.retryable,
|
|
851
856
|
source,
|
|
852
|
-
details: zodDetails(error),
|
|
857
|
+
details: zodDetails(error, redact),
|
|
853
858
|
},
|
|
854
859
|
};
|
|
855
860
|
}
|
|
@@ -879,6 +884,52 @@ function toErrorResponse(error, requestId, observabilityDetails) {
|
|
|
879
884
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
880
885
|
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
881
886
|
// from a duplicate SDK module instance.
|
|
887
|
+
function ownErrorCode(error) {
|
|
888
|
+
if (error === null || (typeof error !== "object" && typeof error !== "function")) {
|
|
889
|
+
return undefined;
|
|
890
|
+
}
|
|
891
|
+
try {
|
|
892
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "code");
|
|
893
|
+
return descriptor && Object.hasOwn(descriptor, "value") && typeof descriptor.value === "string"
|
|
894
|
+
? descriptor.value
|
|
895
|
+
: undefined;
|
|
896
|
+
}
|
|
897
|
+
catch {
|
|
898
|
+
return undefined;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function errorChainHasCode(error, expected) {
|
|
902
|
+
const seen = new Set();
|
|
903
|
+
let current = error;
|
|
904
|
+
for (let depth = 0; depth < 5; depth++) {
|
|
905
|
+
if (current === null || (typeof current !== "object" && typeof current !== "function")) {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
if (seen.has(current))
|
|
909
|
+
return false;
|
|
910
|
+
seen.add(current);
|
|
911
|
+
let code;
|
|
912
|
+
try {
|
|
913
|
+
code = providerErrorCode(current) ?? ownErrorCode(current);
|
|
914
|
+
}
|
|
915
|
+
catch {
|
|
916
|
+
code = ownErrorCode(current);
|
|
917
|
+
}
|
|
918
|
+
if (code === expected)
|
|
919
|
+
return true;
|
|
920
|
+
let cause;
|
|
921
|
+
try {
|
|
922
|
+
cause = Object.getOwnPropertyDescriptor(current, "cause");
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
if (!cause || !Object.hasOwn(cause, "value"))
|
|
928
|
+
return false;
|
|
929
|
+
current = cause.value;
|
|
930
|
+
}
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
882
933
|
function providerObservabilityDetails(error, declaredErrorCode) {
|
|
883
934
|
const declaredRetryable = sdkOwnsErrorResolution(error)
|
|
884
935
|
? undefined
|
|
@@ -904,24 +955,43 @@ function providerObservabilityDetails(error, declaredErrorCode) {
|
|
|
904
955
|
retryable: providerErrorOption(error, "retryable") ?? declaredRetryable ?? false,
|
|
905
956
|
};
|
|
906
957
|
}
|
|
958
|
+
// Native proxy resolution throws ProxyResolutionError directly, while HTTP
|
|
959
|
+
// normalizes it beneath a transport_network_error cause and stealth retains
|
|
960
|
+
// the original code. Preserve proxy attribution across all three forms.
|
|
961
|
+
if (errorChainHasCode(error, "PROXY_REQUIRED")) {
|
|
962
|
+
return {
|
|
963
|
+
category: "proxy_pool",
|
|
964
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
965
|
+
retryable: true,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
907
968
|
if (!isTransportError(error)) {
|
|
908
969
|
return undefined;
|
|
909
970
|
}
|
|
910
|
-
const
|
|
911
|
-
|
|
912
|
-
|
|
971
|
+
const errorCode = providerErrorCode(error);
|
|
972
|
+
const isProxyPoolCode = errorCode === PROXY_POOL_EXHAUSTED_CODE ||
|
|
973
|
+
errorCode === PROXY_EDGE_AUTH_REJECTED_CODE ||
|
|
974
|
+
errorCode === "PROXY_ALLOCATION_FAILED" ||
|
|
975
|
+
errorCode === "native_proxy_expired" ||
|
|
976
|
+
errorCode === "native_proxy_invalid";
|
|
913
977
|
const category = providerErrorOption(error, "category") ??
|
|
914
978
|
(isProxyPoolCode
|
|
915
979
|
? "proxy_pool"
|
|
916
|
-
:
|
|
980
|
+
: errorCode === PROXY_AUTH_IP_DENIED_CODE
|
|
917
981
|
? "anti_bot_blocked"
|
|
918
|
-
:
|
|
982
|
+
: errorCode === "transport_timeout" ||
|
|
983
|
+
errorCode === "native_connection_timeout" ||
|
|
984
|
+
errorCode === "native_connection_idle_timeout"
|
|
919
985
|
? "timeout"
|
|
920
|
-
:
|
|
986
|
+
: errorCode === "transport_network_error" ||
|
|
987
|
+
errorCode === "native_connection_failed" ||
|
|
988
|
+
errorCode === "native_connection_closed"
|
|
921
989
|
? "network"
|
|
922
|
-
:
|
|
923
|
-
?
|
|
924
|
-
:
|
|
990
|
+
: errorCode === "native_connection_aborted"
|
|
991
|
+
? "client_cancelled"
|
|
992
|
+
: error.upstreamStatus
|
|
993
|
+
? categoryForStatus(error.upstreamStatus)
|
|
994
|
+
: "upstream_http");
|
|
925
995
|
return {
|
|
926
996
|
category,
|
|
927
997
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
@@ -1097,13 +1167,13 @@ function extractRequestId(raw) {
|
|
|
1097
1167
|
}
|
|
1098
1168
|
const MAX_PROVIDER_ERROR_CAUSE_FRAMES = 5;
|
|
1099
1169
|
const MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH = 300;
|
|
1100
|
-
function providerErrorCauseMessage(message) {
|
|
1101
|
-
const sanitized = sanitizeDiagnosticText(message);
|
|
1170
|
+
function providerErrorCauseMessage(message, redact) {
|
|
1171
|
+
const sanitized = sanitizeDiagnosticText(redactDiagnosticText(message, redact));
|
|
1102
1172
|
return sanitized.length > MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH
|
|
1103
1173
|
? `${sanitized.slice(0, MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH)}… [truncated]`
|
|
1104
1174
|
: sanitized;
|
|
1105
1175
|
}
|
|
1106
|
-
function providerErrorCauseChain(error) {
|
|
1176
|
+
function providerErrorCauseChain(error, redact) {
|
|
1107
1177
|
if (!(error instanceof Error) && !isProviderError(error))
|
|
1108
1178
|
return undefined;
|
|
1109
1179
|
const seen = new Set([error]);
|
|
@@ -1117,9 +1187,9 @@ function providerErrorCauseChain(error) {
|
|
|
1117
1187
|
const providerObservability = safeProviderErrorObservability(cause);
|
|
1118
1188
|
const causeCode = providerErrorCode(cause);
|
|
1119
1189
|
frames.push({
|
|
1120
|
-
errorClass: cause.name,
|
|
1121
|
-
...(causeCode !== undefined ? { code: causeCode } : {}),
|
|
1122
|
-
message: providerErrorCauseMessage(message),
|
|
1190
|
+
errorClass: redactDiagnosticText(cause.name, redact),
|
|
1191
|
+
...(causeCode !== undefined ? { code: redactDiagnosticText(causeCode, redact) } : {}),
|
|
1192
|
+
message: providerErrorCauseMessage(message, redact),
|
|
1123
1193
|
messageLength: message.length,
|
|
1124
1194
|
messageFingerprint: createHash("sha256").update(message).digest("hex").slice(0, 12),
|
|
1125
1195
|
...(providerObservability ? { providerObservability } : {}),
|
|
@@ -1128,7 +1198,7 @@ function providerErrorCauseChain(error) {
|
|
|
1128
1198
|
}
|
|
1129
1199
|
return frames.length > 0 ? frames : undefined;
|
|
1130
1200
|
}
|
|
1131
|
-
function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode, telemetry, observabilityDetails, correlation = {}) {
|
|
1201
|
+
function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode, telemetry, observabilityDetails, correlation = {}, redact) {
|
|
1132
1202
|
const providerCode = isProviderError(error) ? providerErrorCode(error) : undefined;
|
|
1133
1203
|
const code = isProviderError(error)
|
|
1134
1204
|
? (providerCode ?? "provider_error")
|
|
@@ -1139,7 +1209,7 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
1139
1209
|
: "internal_error";
|
|
1140
1210
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
1141
1211
|
const message = error instanceof Error ? error.message : String(error);
|
|
1142
|
-
const causeChain = providerErrorCauseChain(error);
|
|
1212
|
+
const causeChain = providerErrorCauseChain(error, redact);
|
|
1143
1213
|
const details = observabilityDetails;
|
|
1144
1214
|
const isUnregisteredProviderErrorCode = status === 500 &&
|
|
1145
1215
|
isProviderError(error) &&
|
|
@@ -1158,24 +1228,28 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
1158
1228
|
emit({
|
|
1159
1229
|
level: status >= 500 ? "error" : "warn",
|
|
1160
1230
|
event: "provider_request_failed",
|
|
1161
|
-
providerId: provider.id,
|
|
1231
|
+
providerId: redactDiagnosticText(provider.id, redact),
|
|
1162
1232
|
kind,
|
|
1163
|
-
route,
|
|
1164
|
-
...(requestId ? { requestId } : {}),
|
|
1233
|
+
route: redactCriticalDiagnosticText(route, redact),
|
|
1234
|
+
...(requestId ? { requestId: redactCriticalDiagnosticText(requestId, redact) } : {}),
|
|
1165
1235
|
...(correlation.connectionId !== undefined
|
|
1166
|
-
? { connectionId: correlation.connectionId }
|
|
1236
|
+
? { connectionId: redactDiagnosticText(correlation.connectionId, redact) }
|
|
1237
|
+
: {}),
|
|
1238
|
+
...(correlation.flowId !== undefined
|
|
1239
|
+
? { flowId: redactDiagnosticText(correlation.flowId, redact) }
|
|
1240
|
+
: {}),
|
|
1241
|
+
...(correlation.tenantId !== undefined
|
|
1242
|
+
? { tenantId: redactDiagnosticText(correlation.tenantId, redact) }
|
|
1167
1243
|
: {}),
|
|
1168
|
-
...(correlation.flowId !== undefined ? { flowId: correlation.flowId } : {}),
|
|
1169
|
-
...(correlation.tenantId !== undefined ? { tenantId: correlation.tenantId } : {}),
|
|
1170
1244
|
...(correlation.requestedProviderId !== undefined
|
|
1171
|
-
? { requestedProviderId: correlation.requestedProviderId }
|
|
1245
|
+
? { requestedProviderId: redactDiagnosticText(correlation.requestedProviderId, redact) }
|
|
1172
1246
|
: {}),
|
|
1173
1247
|
status,
|
|
1174
1248
|
...cost,
|
|
1175
1249
|
...(telemetryPayload ?? {}),
|
|
1176
|
-
code,
|
|
1177
|
-
errorClass,
|
|
1178
|
-
message,
|
|
1250
|
+
code: sanitizeSpanNameForOutput(redactCriticalDiagnosticText(code, redact)),
|
|
1251
|
+
errorClass: redactDiagnosticText(errorClass, redact),
|
|
1252
|
+
message: sanitizeDiagnosticText(redactDiagnosticText(message, redact)),
|
|
1179
1253
|
...(causeChain ? { causeChain } : {}),
|
|
1180
1254
|
...(providerObservability ? { providerObservability } : {}),
|
|
1181
1255
|
...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
|
|
@@ -1188,42 +1262,46 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
1188
1262
|
signalFix: "Declare this code (with status and retryable) in the operation's errorCodes so it serves its intended status instead of 500.",
|
|
1189
1263
|
}
|
|
1190
1264
|
: {}),
|
|
1191
|
-
...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
|
|
1265
|
+
...(error instanceof z.ZodError ? { issues: zodDetails(error, redact) } : {}),
|
|
1192
1266
|
});
|
|
1193
1267
|
}
|
|
1194
|
-
function logProviderCleanupError(logger, provider, kind, operationId, requestId, resource, error) {
|
|
1268
|
+
function logProviderCleanupError(logger, provider, kind, operationId, requestId, resource, error, redact) {
|
|
1195
1269
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
1196
1270
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
1197
1271
|
const message = error instanceof Error ? error.message : String(error);
|
|
1198
1272
|
emit({
|
|
1199
1273
|
level: "warn",
|
|
1200
1274
|
event: "provider_cleanup_failed",
|
|
1201
|
-
providerId: provider.id,
|
|
1275
|
+
providerId: redactDiagnosticText(provider.id, redact),
|
|
1202
1276
|
kind,
|
|
1203
|
-
route: operationId,
|
|
1204
|
-
...(requestId ? { requestId } : {}),
|
|
1277
|
+
route: redactCriticalDiagnosticText(operationId, redact),
|
|
1278
|
+
...(requestId ? { requestId: redactCriticalDiagnosticText(requestId, redact) } : {}),
|
|
1205
1279
|
resource,
|
|
1206
|
-
errorClass,
|
|
1207
|
-
message,
|
|
1280
|
+
errorClass: redactDiagnosticText(errorClass, redact),
|
|
1281
|
+
message: sanitizeDiagnosticText(redactDiagnosticText(message, redact)),
|
|
1208
1282
|
});
|
|
1209
1283
|
}
|
|
1210
|
-
function logProviderSuccess(logger, provider, kind, route, requestId, status, cost, telemetry, correlation = {}) {
|
|
1284
|
+
function logProviderSuccess(logger, provider, kind, route, requestId, status, cost, telemetry, correlation = {}, redact) {
|
|
1211
1285
|
const telemetryPayload = telemetry?.toLogPayload();
|
|
1212
1286
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
1213
1287
|
emit({
|
|
1214
1288
|
level: "info",
|
|
1215
1289
|
event: "provider_request_completed",
|
|
1216
|
-
providerId: provider.id,
|
|
1290
|
+
providerId: redactDiagnosticText(provider.id, redact),
|
|
1217
1291
|
kind,
|
|
1218
|
-
route,
|
|
1219
|
-
...(requestId ? { requestId } : {}),
|
|
1292
|
+
route: redactCriticalDiagnosticText(route, redact),
|
|
1293
|
+
...(requestId ? { requestId: redactCriticalDiagnosticText(requestId, redact) } : {}),
|
|
1220
1294
|
...(correlation.connectionId !== undefined
|
|
1221
|
-
? { connectionId: correlation.connectionId }
|
|
1295
|
+
? { connectionId: redactDiagnosticText(correlation.connectionId, redact) }
|
|
1296
|
+
: {}),
|
|
1297
|
+
...(correlation.flowId !== undefined
|
|
1298
|
+
? { flowId: redactDiagnosticText(correlation.flowId, redact) }
|
|
1299
|
+
: {}),
|
|
1300
|
+
...(correlation.tenantId !== undefined
|
|
1301
|
+
? { tenantId: redactDiagnosticText(correlation.tenantId, redact) }
|
|
1222
1302
|
: {}),
|
|
1223
|
-
...(correlation.flowId !== undefined ? { flowId: correlation.flowId } : {}),
|
|
1224
|
-
...(correlation.tenantId !== undefined ? { tenantId: correlation.tenantId } : {}),
|
|
1225
1303
|
...(correlation.requestedProviderId !== undefined
|
|
1226
|
-
? { requestedProviderId: correlation.requestedProviderId }
|
|
1304
|
+
? { requestedProviderId: redactDiagnosticText(correlation.requestedProviderId, redact) }
|
|
1227
1305
|
: {}),
|
|
1228
1306
|
status,
|
|
1229
1307
|
...cost,
|
|
@@ -1276,9 +1354,106 @@ function requestTraceId(headers, requestId) {
|
|
|
1276
1354
|
? createHash("sha256").update(requestId, "utf8").digest("hex").slice(0, 32)
|
|
1277
1355
|
: undefined));
|
|
1278
1356
|
}
|
|
1357
|
+
/** Harvest only credential subtrees, iteratively and before schema diagnostics. */
|
|
1358
|
+
function rawRequestCredentials(rawBody, kind) {
|
|
1359
|
+
const record = (value) => value !== null && typeof value === "object" ? value : undefined;
|
|
1360
|
+
const own = (value, key) => {
|
|
1361
|
+
const object = record(value);
|
|
1362
|
+
return object && Object.hasOwn(object, key) ? object[key] : undefined;
|
|
1363
|
+
};
|
|
1364
|
+
const body = kind === "stateful" ? own(rawBody, "operationRequest") : rawBody;
|
|
1365
|
+
const values = [];
|
|
1366
|
+
let remainingBytes = 64 * 1024;
|
|
1367
|
+
let exhausted = false;
|
|
1368
|
+
const add = (value) => {
|
|
1369
|
+
if (typeof value !== "string")
|
|
1370
|
+
return;
|
|
1371
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
1372
|
+
if (bytes > remainingBytes) {
|
|
1373
|
+
exhausted = true;
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
remainingBytes -= bytes;
|
|
1377
|
+
if (value)
|
|
1378
|
+
values.push(value);
|
|
1379
|
+
const trimmed = value.trim();
|
|
1380
|
+
if (trimmed && trimmed !== value)
|
|
1381
|
+
values.push(trimmed);
|
|
1382
|
+
};
|
|
1383
|
+
function* entries(object) {
|
|
1384
|
+
for (const key in object) {
|
|
1385
|
+
if (Object.hasOwn(object, key))
|
|
1386
|
+
yield [key, object[key]];
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
const visit = (root) => {
|
|
1390
|
+
const object = record(root);
|
|
1391
|
+
if (!object) {
|
|
1392
|
+
add(root);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
// Connection inventories are usually flat. Walk the root directly and only
|
|
1396
|
+
// allocate iterator frames when a nested credential container is encountered.
|
|
1397
|
+
for (const key in object) {
|
|
1398
|
+
if (!Object.hasOwn(object, key))
|
|
1399
|
+
continue;
|
|
1400
|
+
const value = object[key];
|
|
1401
|
+
add(key);
|
|
1402
|
+
if (exhausted)
|
|
1403
|
+
break;
|
|
1404
|
+
const child = record(value);
|
|
1405
|
+
if (!child) {
|
|
1406
|
+
add(value);
|
|
1407
|
+
if (exhausted)
|
|
1408
|
+
break;
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
const stack = [{ iterator: entries(child), depth: 1 }];
|
|
1412
|
+
while (stack.length && !exhausted) {
|
|
1413
|
+
const frame = stack[stack.length - 1];
|
|
1414
|
+
const entry = frame.iterator.next();
|
|
1415
|
+
if (entry.done) {
|
|
1416
|
+
stack.pop();
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
const [key, value] = entry.value;
|
|
1420
|
+
add(key);
|
|
1421
|
+
if (exhausted)
|
|
1422
|
+
break;
|
|
1423
|
+
const child = record(value);
|
|
1424
|
+
if (child) {
|
|
1425
|
+
if (frame.depth >= 8) {
|
|
1426
|
+
exhausted = true;
|
|
1427
|
+
break;
|
|
1428
|
+
}
|
|
1429
|
+
stack.push({ iterator: entries(child), depth: frame.depth + 1 });
|
|
1430
|
+
}
|
|
1431
|
+
else
|
|
1432
|
+
add(value);
|
|
1433
|
+
}
|
|
1434
|
+
if (exhausted)
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
visit(own(own(body, "connection"), "secrets"));
|
|
1439
|
+
if (kind === "auth" && !exhausted) {
|
|
1440
|
+
add(own(own(body, "engine"), "egressLease"));
|
|
1441
|
+
if (!exhausted)
|
|
1442
|
+
visit(own(body, "input"));
|
|
1443
|
+
}
|
|
1444
|
+
return { values, exhausted };
|
|
1445
|
+
}
|
|
1279
1446
|
function createRequestScope(input) {
|
|
1447
|
+
const sensitiveRegistry = createDiagnosticRedactor([], input.staticSensitiveValues);
|
|
1448
|
+
const redact = (text) => redactDiagnosticText(text, sensitiveRegistry.redact);
|
|
1449
|
+
bindCriticalDiagnosticRedactor(redact, sensitiveRegistry.redactCritical, sensitiveRegistry.redactStructured, () => sensitiveRegistry.suppressed);
|
|
1280
1450
|
const requestCost = startRequestCost();
|
|
1281
|
-
const
|
|
1451
|
+
const observeEnv = {
|
|
1452
|
+
observe: createDiagnosticEnvObserver(input.provider, sensitiveRegistry.add, sensitiveRegistry.has),
|
|
1453
|
+
finished: false,
|
|
1454
|
+
staticValues: input.staticSensitiveValues,
|
|
1455
|
+
};
|
|
1456
|
+
const traceConfig = withDiagnosticEnv(observeEnv, () => resolveTraceConfigFromEnv());
|
|
1282
1457
|
const details = {
|
|
1283
1458
|
route: input.route,
|
|
1284
1459
|
requestId: input.requestId,
|
|
@@ -1297,15 +1472,19 @@ function createRequestScope(input) {
|
|
|
1297
1472
|
const initialTraceId = requestTraceId(input.headers, input.requestId);
|
|
1298
1473
|
const trace = traceConfig
|
|
1299
1474
|
? createTraceContext({
|
|
1300
|
-
...resolveServerTraceContextOptions(traceConfig, traceAttributes),
|
|
1475
|
+
...withDiagnosticEnv(observeEnv, () => resolveServerTraceContextOptions(traceConfig, traceAttributes, undefined, redact)),
|
|
1476
|
+
redact,
|
|
1301
1477
|
...(initialTraceId ? { traceId: initialTraceId } : {}),
|
|
1302
1478
|
})
|
|
1303
|
-
: createTraceContext();
|
|
1479
|
+
: createTraceContext({ redact });
|
|
1480
|
+
bindDiagnosticSensitiveRegistry(trace, sensitiveRegistry);
|
|
1304
1481
|
const proxyCollector = new ProxyTelemetryCollector();
|
|
1305
|
-
const resolverCollector = new ResolverTelemetryCollector();
|
|
1482
|
+
const resolverCollector = new ResolverTelemetryCollector({ redact });
|
|
1483
|
+
const nativeCollector = new NativeTelemetryCollector({ redact });
|
|
1306
1484
|
const telemetry = new RequestTelemetry(trace);
|
|
1307
1485
|
telemetry.register(proxyCollector);
|
|
1308
1486
|
telemetry.register(resolverCollector);
|
|
1487
|
+
telemetry.register(nativeCollector);
|
|
1309
1488
|
let rootRunner = (fn) => fn();
|
|
1310
1489
|
let resolveRoot;
|
|
1311
1490
|
const rootTerminal = new Promise((resolve) => {
|
|
@@ -1326,13 +1505,13 @@ function createRequestScope(input) {
|
|
|
1326
1505
|
return clientCancelledError();
|
|
1327
1506
|
return undefined;
|
|
1328
1507
|
};
|
|
1329
|
-
const root = trace.span(`request:${input.kind}:${input.route}`, async () => {
|
|
1508
|
+
const root = withDiagnosticEnv(observeEnv, () => trace.span(`request:${input.kind}:${input.route}`, async () => {
|
|
1330
1509
|
rootRunner = AsyncLocalStorage.snapshot();
|
|
1331
1510
|
const outcome = await rootTerminal;
|
|
1332
1511
|
const error = terminalError(outcome);
|
|
1333
1512
|
if (error !== undefined)
|
|
1334
1513
|
throw error;
|
|
1335
|
-
});
|
|
1514
|
+
}));
|
|
1336
1515
|
void root.catch(() => undefined);
|
|
1337
1516
|
const settleRoot = () => {
|
|
1338
1517
|
if (!terminalOutcome || streamingSetupPending || rootSettled)
|
|
@@ -1368,7 +1547,7 @@ function createRequestScope(input) {
|
|
|
1368
1547
|
traceAttributes.flow_id = details.flowId;
|
|
1369
1548
|
traceAttributes.route = details.route;
|
|
1370
1549
|
const traceId = requestTraceId(details.headers, details.requestId);
|
|
1371
|
-
const sanitizedAttributes = Object.fromEntries(Object.entries(sanitizeTraceAttributes(traceAttributes)).map(([key, value]) => [
|
|
1550
|
+
const sanitizedAttributes = Object.fromEntries(Object.entries(sanitizeTraceAttributes(traceAttributes, redact)).map(([key, value]) => [
|
|
1372
1551
|
key,
|
|
1373
1552
|
String(value),
|
|
1374
1553
|
]));
|
|
@@ -1378,9 +1557,18 @@ function createRequestScope(input) {
|
|
|
1378
1557
|
});
|
|
1379
1558
|
};
|
|
1380
1559
|
const scope = {
|
|
1560
|
+
redact,
|
|
1381
1561
|
trace,
|
|
1382
1562
|
telemetry,
|
|
1383
1563
|
resolverTelemetry: resolverCollector,
|
|
1564
|
+
nativeTelemetry: nativeCollector,
|
|
1565
|
+
seedCredentials(rawBody, kind) {
|
|
1566
|
+
const harvested = rawRequestCredentials(rawBody, kind);
|
|
1567
|
+
sensitiveRegistry.add(harvested.values);
|
|
1568
|
+
// An incomplete inventory cannot certify free text: emit less.
|
|
1569
|
+
if (harvested.exhausted)
|
|
1570
|
+
sensitiveRegistry.suppress();
|
|
1571
|
+
},
|
|
1384
1572
|
enrich(enrichment) {
|
|
1385
1573
|
if (terminalOutcome)
|
|
1386
1574
|
return;
|
|
@@ -1442,21 +1630,21 @@ function createRequestScope(input) {
|
|
|
1442
1630
|
terminalize(outcome) {
|
|
1443
1631
|
if (finishedResult)
|
|
1444
1632
|
return finishedResult;
|
|
1633
|
+
observeEnv.finished = true;
|
|
1445
1634
|
terminalOutcome = outcome;
|
|
1446
1635
|
const error = terminalError(outcome);
|
|
1447
1636
|
finishedResult = {};
|
|
1448
1637
|
try {
|
|
1449
1638
|
const declaredErrorCode = error === undefined ? undefined : input.declaredErrorCode?.(error);
|
|
1450
|
-
const status = outcome.status ??
|
|
1451
|
-
(error === undefined ? 200 : toStatusCode(error, declaredErrorCode));
|
|
1639
|
+
const status = outcome.status ?? (error === undefined ? 200 : toStatusCode(error, declaredErrorCode));
|
|
1452
1640
|
finishedResult = headerSnapshot(error);
|
|
1453
1641
|
const cost = finishRequestCost(requestCost);
|
|
1454
1642
|
try {
|
|
1455
1643
|
if (error === undefined) {
|
|
1456
|
-
logProviderSuccess(input.logger, input.provider, input.kind, details.route, details.requestId, status, cost, telemetry, details.correlation);
|
|
1644
|
+
logProviderSuccess(input.logger, input.provider, input.kind, details.route, details.requestId, status, cost, telemetry, details.correlation, redact);
|
|
1457
1645
|
}
|
|
1458
1646
|
else {
|
|
1459
|
-
logProviderError(input.logger, input.provider, input.kind, details.route, details.requestId, error, status, cost, declaredErrorCode, telemetry, finishedResult.errorObservability, details.correlation);
|
|
1647
|
+
logProviderError(input.logger, input.provider, input.kind, details.route, details.requestId, error, status, cost, declaredErrorCode, telemetry, finishedResult.errorObservability, details.correlation, redact);
|
|
1460
1648
|
}
|
|
1461
1649
|
}
|
|
1462
1650
|
catch {
|
|
@@ -1490,8 +1678,9 @@ function createRequestScope(input) {
|
|
|
1490
1678
|
};
|
|
1491
1679
|
return scope;
|
|
1492
1680
|
}
|
|
1493
|
-
// A raw `Response` result skips `toErrorResponse
|
|
1494
|
-
// provider-owned
|
|
1681
|
+
// A raw `Response` result skips `toErrorResponse`. Its payload shape stays
|
|
1682
|
+
// provider-owned; known credentials in error payloads still need redaction.
|
|
1683
|
+
// The observability header is SDK-owned: derive it
|
|
1495
1684
|
// from the status. Only timeouts, rate limits, and 5xx are retryable;
|
|
1496
1685
|
// `isRetryableCategory` would otherwise mark every other 4xx (`upstream_http`)
|
|
1497
1686
|
// as retryable.
|
|
@@ -1505,7 +1694,7 @@ function rawResponseErrorObservability(status) {
|
|
|
1505
1694
|
// `rawStatusFallback` is decided by the caller from the outcome, not from a
|
|
1506
1695
|
// missing snapshot: a failed outcome whose thrown value is `undefined` also has
|
|
1507
1696
|
// no `errorObservability`, and its 500 carries the SDK envelope, not a raw body.
|
|
1508
|
-
function responseWithRequestScopeHeaders(response, finished, rawStatusFallback) {
|
|
1697
|
+
async function responseWithRequestScopeHeaders(response, finished, rawStatusFallback, redact) {
|
|
1509
1698
|
const headers = new Headers(response.headers);
|
|
1510
1699
|
headers.delete(PROVIDER_TELEMETRY_HEADER);
|
|
1511
1700
|
if (finished.providerTelemetryHeader) {
|
|
@@ -1519,16 +1708,25 @@ function responseWithRequestScopeHeaders(response, finished, rawStatusFallback)
|
|
|
1519
1708
|
if (errorObservability) {
|
|
1520
1709
|
headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservability));
|
|
1521
1710
|
}
|
|
1522
|
-
|
|
1711
|
+
// Raw Response bodies are provider-owned. Preserve their stream and representation.
|
|
1712
|
+
const body = response.body;
|
|
1713
|
+
const statusText = rawStatusFallback && response.status >= 400
|
|
1714
|
+
? redactDiagnosticText(response.statusText, redact)
|
|
1715
|
+
: response.statusText;
|
|
1716
|
+
return new Response(body, {
|
|
1523
1717
|
headers,
|
|
1524
1718
|
status: response.status,
|
|
1525
|
-
|
|
1719
|
+
// When free text cannot be certified, use the SDK's standard HTTP reason
|
|
1720
|
+
// phrase. It preserves the status contract without trusting provider text.
|
|
1721
|
+
statusText: statusText === REDACTION_FAILED && isDiagnosticRedactionSuppressed(redact)
|
|
1722
|
+
? (STATUS_CODES[response.status] ?? "")
|
|
1723
|
+
: statusText,
|
|
1526
1724
|
});
|
|
1527
1725
|
}
|
|
1528
|
-
function finalizeRequestResponse(scope, response, outcome) {
|
|
1726
|
+
async function finalizeRequestResponse(scope, response, outcome) {
|
|
1529
1727
|
try {
|
|
1530
1728
|
const error = outcome.kind === "failed" ? outcome.error : undefined;
|
|
1531
|
-
const finalResponse = responseWithRequestScopeHeaders(response, scope.snapshotHeaders(error), outcome.kind === "completed");
|
|
1729
|
+
const finalResponse = await responseWithRequestScopeHeaders(response, scope.snapshotHeaders(error), outcome.kind === "completed", scope.redact);
|
|
1532
1730
|
scope.terminalize(outcome);
|
|
1533
1731
|
return finalResponse;
|
|
1534
1732
|
}
|
|
@@ -1706,7 +1904,7 @@ function toSseResponse(operation, result, lifecycle, requestId) {
|
|
|
1706
1904
|
}
|
|
1707
1905
|
catch (error) {
|
|
1708
1906
|
const message = error instanceof Error ? error.message : "Stream failed";
|
|
1709
|
-
controller.enqueue(encoder.encode(encodeSseEvent(streamError("stream_error", message, {
|
|
1907
|
+
controller.enqueue(encoder.encode(encodeSseEvent(streamError("stream_error", sanitizeDiagnosticText(redactDiagnosticText(message, lifecycle.redact)), {
|
|
1710
1908
|
...(requestId ? { requestId } : {}),
|
|
1711
1909
|
}))));
|
|
1712
1910
|
controller.close();
|
|
@@ -1886,7 +2084,7 @@ async function handleOperation(provider, request, operationId, options, state =
|
|
|
1886
2084
|
ctx.stealth.close?.();
|
|
1887
2085
|
}
|
|
1888
2086
|
catch (error) {
|
|
1889
|
-
logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error);
|
|
2087
|
+
logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error, scope.redact);
|
|
1890
2088
|
}
|
|
1891
2089
|
}
|
|
1892
2090
|
if ("browser" in ctx) {
|
|
@@ -1894,11 +2092,12 @@ async function handleOperation(provider, request, operationId, options, state =
|
|
|
1894
2092
|
await ctx.browser.close?.();
|
|
1895
2093
|
}
|
|
1896
2094
|
catch (error) {
|
|
1897
|
-
logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "browser", error);
|
|
2095
|
+
logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "browser", error, scope.redact);
|
|
1898
2096
|
}
|
|
1899
2097
|
}
|
|
1900
2098
|
};
|
|
1901
2099
|
const streamLifecycle = {
|
|
2100
|
+
redact: scope.redact,
|
|
1902
2101
|
runStep(fn) {
|
|
1903
2102
|
return scope.runStreamStep(fn);
|
|
1904
2103
|
},
|
|
@@ -1996,7 +2195,7 @@ async function handleAuthFlow(provider, request, route, options, state, scope, s
|
|
|
1996
2195
|
context.stealth.close?.();
|
|
1997
2196
|
}
|
|
1998
2197
|
catch (error) {
|
|
1999
|
-
logProviderCleanupError(options.logger, provider, "auth", route, request.requestId, "stealth", error);
|
|
2198
|
+
logProviderCleanupError(options.logger, provider, "auth", route, request.requestId, "stealth", error, scope.redact);
|
|
2000
2199
|
}
|
|
2001
2200
|
}
|
|
2002
2201
|
}
|
|
@@ -2102,13 +2301,13 @@ function operationRequestFromForwardingEnvelope(envelope) {
|
|
|
2102
2301
|
...(envelope.deadlineAt !== undefined ? { deadlineAt: envelope.deadlineAt } : {}),
|
|
2103
2302
|
};
|
|
2104
2303
|
}
|
|
2105
|
-
function parseStatefulForwardingEnvelope(rawBody) {
|
|
2304
|
+
function parseStatefulForwardingEnvelope(rawBody, redact) {
|
|
2106
2305
|
const parsed = ProviderServerStatefulForwardEnvelopeSchema.safeParse(rawBody);
|
|
2107
2306
|
if (parsed.success)
|
|
2108
2307
|
return parsed.data;
|
|
2109
2308
|
throw new ProviderError("Stateful forwarding envelope is invalid.", {
|
|
2110
2309
|
code: "STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
2111
|
-
details: zodDetails(parsed.error),
|
|
2310
|
+
details: zodDetails(parsed.error, redact),
|
|
2112
2311
|
});
|
|
2113
2312
|
}
|
|
2114
2313
|
/**
|
|
@@ -2142,6 +2341,10 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2142
2341
|
engine: serverOptions.engine ?? createInProcessProviderEngine(),
|
|
2143
2342
|
};
|
|
2144
2343
|
const app = new Hono();
|
|
2344
|
+
// Compile the startup inventory once, composed with the shared outside-context fallback.
|
|
2345
|
+
const staticSensitiveValues = compileProcessDiagnosticSensitiveValues(collectStaticDiagnosticSensitiveValues(provider, {
|
|
2346
|
+
statefulForwardingSecret: options.statefulForwarding?.secret,
|
|
2347
|
+
}));
|
|
2145
2348
|
const logger = options.logger ?? defaultProviderServerLogger;
|
|
2146
2349
|
const operationErrorCodes = buildOperationErrorCodeLookup(provider);
|
|
2147
2350
|
const statefulForwardingReplayCache = new StatefulForwardingReplayCache(options.statefulForwarding?.replayCacheMaxEntries ??
|
|
@@ -2182,6 +2385,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2182
2385
|
const operation = "stateful-internal";
|
|
2183
2386
|
const requestScope = createRequestScope({
|
|
2184
2387
|
provider,
|
|
2388
|
+
staticSensitiveValues,
|
|
2185
2389
|
kind: "operation",
|
|
2186
2390
|
route: operation,
|
|
2187
2391
|
headers: Object.fromEntries(c.req.raw.headers.entries()),
|
|
@@ -2212,7 +2416,8 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2212
2416
|
code: "STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
2213
2417
|
});
|
|
2214
2418
|
}
|
|
2215
|
-
|
|
2419
|
+
requestScope.seedCredentials(rawBody, "stateful");
|
|
2420
|
+
const envelope = parseStatefulForwardingEnvelope(rawBody, requestScope.redact);
|
|
2216
2421
|
if (envelope.providerId !== provider.id) {
|
|
2217
2422
|
throw new ProviderError("Stateful forwarding envelope providerId does not match the served provider.", { code: "STATEFUL_FORWARDING_PROVIDER_MISMATCH" });
|
|
2218
2423
|
}
|
|
@@ -2313,7 +2518,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2313
2518
|
...(requestId ? { requestId } : {}),
|
|
2314
2519
|
});
|
|
2315
2520
|
const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
|
|
2316
|
-
const response = c.json(toErrorResponse(error, requestId, observabilityDetails), status);
|
|
2521
|
+
const response = c.json(toErrorResponse(error, requestId, observabilityDetails, requestScope.redact), status);
|
|
2317
2522
|
return finalizeRequestResponse(requestScope, response, {
|
|
2318
2523
|
kind: "failed",
|
|
2319
2524
|
status,
|
|
@@ -2326,6 +2531,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2326
2531
|
const operation = c.req.param("operation");
|
|
2327
2532
|
const requestScope = createRequestScope({
|
|
2328
2533
|
provider,
|
|
2534
|
+
staticSensitiveValues,
|
|
2329
2535
|
kind: "operation",
|
|
2330
2536
|
route: operation,
|
|
2331
2537
|
operationId: operation,
|
|
@@ -2339,6 +2545,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2339
2545
|
.clone()
|
|
2340
2546
|
.json()
|
|
2341
2547
|
.catch(() => undefined);
|
|
2548
|
+
requestScope.seedCredentials(rawBody, "operation");
|
|
2342
2549
|
const body = OperationRequestSchema.parse(rawBody);
|
|
2343
2550
|
const requestHeaders = Object.fromEntries(c.req.raw.headers.entries());
|
|
2344
2551
|
body.headers = { ...requestHeaders, ...body.headers };
|
|
@@ -2356,7 +2563,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2356
2563
|
const handled = await handleOperation(provider, body, operation, options, state, requestScope, c.req.raw.signal);
|
|
2357
2564
|
const response = handled instanceof Response ? handled : c.json(handled);
|
|
2358
2565
|
return streaming
|
|
2359
|
-
? responseWithRequestScopeHeaders(response, requestScope.snapshotHeaders(), true)
|
|
2566
|
+
? responseWithRequestScopeHeaders(response, requestScope.snapshotHeaders(), true, requestScope.redact)
|
|
2360
2567
|
: response;
|
|
2361
2568
|
};
|
|
2362
2569
|
const response = streaming
|
|
@@ -2375,7 +2582,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2375
2582
|
const requestId = extractRequestId(rawBody);
|
|
2376
2583
|
requestScope.enrich({ ...(requestId ? { requestId } : {}) });
|
|
2377
2584
|
const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
|
|
2378
|
-
const response = c.json(toErrorResponse(error, requestId, observabilityDetails), status);
|
|
2585
|
+
const response = c.json(toErrorResponse(error, requestId, observabilityDetails, requestScope.redact), status);
|
|
2379
2586
|
return finalizeRequestResponse(requestScope, response, {
|
|
2380
2587
|
kind: "failed",
|
|
2381
2588
|
status,
|
|
@@ -2395,6 +2602,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2395
2602
|
let rawBody;
|
|
2396
2603
|
const requestScope = createRequestScope({
|
|
2397
2604
|
provider,
|
|
2605
|
+
staticSensitiveValues,
|
|
2398
2606
|
kind: "auth",
|
|
2399
2607
|
route: logRoute,
|
|
2400
2608
|
headers: Object.fromEntries(c.req.raw.headers.entries()),
|
|
@@ -2406,6 +2614,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2406
2614
|
.clone()
|
|
2407
2615
|
.json()
|
|
2408
2616
|
.catch(() => undefined);
|
|
2617
|
+
requestScope.seedCredentials(rawBody, "auth");
|
|
2409
2618
|
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
2410
2619
|
requestScope.enrich({
|
|
2411
2620
|
requestId: body.requestId,
|
|
@@ -2434,7 +2643,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2434
2643
|
const requestId = extractRequestId(rawBody);
|
|
2435
2644
|
requestScope.enrich({ ...(requestId ? { requestId } : {}) });
|
|
2436
2645
|
const observabilityDetails = errorObservabilityDetails(error);
|
|
2437
|
-
const response = c.json(toErrorResponse(error, requestId, observabilityDetails), status);
|
|
2646
|
+
const response = c.json(toErrorResponse(error, requestId, observabilityDetails, requestScope.redact), status);
|
|
2438
2647
|
return finalizeRequestResponse(requestScope, response, {
|
|
2439
2648
|
kind: "failed",
|
|
2440
2649
|
status,
|