@apifuse/provider-sdk 2.2.0-beta.58 → 2.2.0-beta.60
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 +25 -0
- package/CHANGELOG.md +8 -0
- package/README.md +18 -2
- package/bin/apifuse-dev.ts +2 -1
- package/bin/apifuse-pack-types.ts +49 -0
- package/bin/apifuse-perf.ts +41 -4
- package/bin/apifuse-record.ts +9 -2
- package/dist/cli/templates/provider/README.md.tpl +15 -3
- package/dist/config/loader.d.ts +7 -0
- package/dist/config/loader.js +43 -18
- package/dist/declaration-validation.d.ts +2 -1
- package/dist/declaration-validation.js +39 -0
- package/dist/define.d.ts +4 -4
- package/dist/define.js +25 -1
- package/dist/engine.d.ts +16 -0
- package/dist/engine.js +59 -3
- package/dist/error-resolution.js +13 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -2
- package/dist/lint.js +38 -17
- package/dist/provider.d.ts +3 -3
- package/dist/provider.js +2 -2
- package/dist/runtime/bounded-expiring-map.d.ts +21 -0
- package/dist/runtime/bounded-expiring-map.js +64 -0
- package/dist/runtime/browser.js +2 -1
- package/dist/runtime/cache.d.ts +2 -0
- package/dist/runtime/cache.js +11 -3
- 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 +65 -0
- package/dist/runtime/egress-lease.js +236 -0
- package/dist/runtime/env.js +2 -1
- package/dist/runtime/http.js +1 -0
- package/dist/runtime/instrumentation.js +3 -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/proxy-retry-policy.d.ts +4 -1
- package/dist/runtime/proxy-retry-policy.js +11 -2
- package/dist/runtime/request-options.d.ts +7 -0
- package/dist/runtime/request-options.js +62 -22
- package/dist/runtime/request-telemetry.d.ts +7 -9
- package/dist/runtime/resolver-config.d.ts +7 -0
- package/dist/runtime/resolver-config.js +8 -0
- package/dist/runtime/resolver-public.d.ts +3 -1
- package/dist/runtime/resolver-public.js +1 -1
- package/dist/runtime/resolver-shared.d.ts +9 -0
- package/dist/runtime/resolver-shared.js +57 -0
- package/dist/runtime/resolver-telemetry.d.ts +134 -0
- package/dist/runtime/resolver-telemetry.js +204 -0
- package/dist/runtime/resolver-usage.d.ts +24 -0
- package/dist/runtime/resolver-usage.js +60 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +5 -0
- package/dist/runtime/resolver-vendors/bindings.js +5 -0
- package/dist/runtime/resolver-vendors/browser.js +0 -1
- package/dist/runtime/resolver-vendors/capsolver.d.ts +2 -2
- package/dist/runtime/resolver-vendors/capsolver.js +21 -9
- package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -2
- package/dist/runtime/resolver-vendors/twocaptcha.js +26 -10
- package/dist/runtime/resolver-vendors/types.d.ts +35 -5
- package/dist/runtime/resolver-vendors/types.js +20 -10
- package/dist/runtime/resolver.d.ts +5 -1
- package/dist/runtime/resolver.js +400 -111
- package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
- package/dist/runtime/stealth-akamai-sbsd.js +142 -0
- package/dist/runtime/stealth.js +547 -53
- 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 +20 -0
- package/dist/schema.js +72 -2
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1 -0
- package/dist/server/self-test-redaction.js +8 -33
- package/dist/server/self-test.d.ts +4 -0
- package/dist/server/self-test.js +10 -1
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +131 -0
- package/dist/server/serve-implementation.d.ts +7 -1
- package/dist/server/serve-implementation.js +394 -125
- package/dist/server/trace-output.d.ts +2 -1
- package/dist/server/trace-output.js +46 -14
- package/dist/server/types.d.ts +6 -0
- package/dist/server/types.js +14 -0
- package/dist/stealth/profiles.d.ts +7 -1
- package/dist/stealth/profiles.js +9 -0
- package/dist/testing/run.js +6 -0
- package/dist/trace-sanitization.d.ts +4 -3
- package/dist/trace-sanitization.js +45 -22
- package/dist/types.d.ts +106 -10
- package/package.json +2 -1
- package/src/cli/templates/provider/README.md.tpl +15 -3
- package/src/config/loader.ts +47 -18
- package/src/declaration-validation.ts +47 -1
- package/src/define.ts +45 -3
- package/src/engine.ts +78 -3
- package/src/error-resolution.ts +13 -0
- package/src/index.ts +34 -1
- package/src/lint.ts +44 -18
- package/src/provider.ts +10 -0
- package/src/runtime/bounded-expiring-map.ts +67 -0
- package/src/runtime/browser.ts +2 -1
- package/src/runtime/cache.ts +13 -3
- 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 +341 -0
- package/src/runtime/env.ts +2 -1
- package/src/runtime/http.ts +1 -0
- package/src/runtime/instrumentation.ts +3 -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/proxy-retry-policy.ts +12 -1
- package/src/runtime/request-options.ts +76 -28
- package/src/runtime/request-telemetry.ts +5 -7
- package/src/runtime/resolver-config.ts +12 -0
- package/src/runtime/resolver-public.ts +21 -0
- package/src/runtime/resolver-shared.ts +75 -0
- package/src/runtime/resolver-telemetry.ts +362 -0
- package/src/runtime/resolver-usage.ts +96 -0
- package/src/runtime/resolver-vendors/bindings.ts +5 -0
- package/src/runtime/resolver-vendors/browser.ts +0 -1
- package/src/runtime/resolver-vendors/capsolver.ts +22 -3
- package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +26 -12
- package/src/runtime/resolver-vendors/types.ts +60 -12
- package/src/runtime/resolver.ts +468 -126
- package/src/runtime/stealth-akamai-sbsd.ts +207 -0
- package/src/runtime/stealth.ts +727 -95
- package/src/runtime/stt.ts +3 -2
- package/src/runtime/trace.ts +168 -26
- package/src/schema.ts +80 -2
- package/src/server/index.ts +21 -0
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/self-test.ts +13 -0
- package/src/server/sensitive-values.ts +200 -0
- package/src/server/serve-implementation.ts +561 -136
- package/src/server/trace-output.ts +77 -17
- package/src/server/types.ts +14 -0
- package/src/stealth/profiles.ts +10 -0
- package/src/testing/run.ts +6 -0
- package/src/trace-sanitization.ts +70 -23
- package/src/types.ts +124 -19
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { STATUS_CODES } from "node:http";
|
|
2
|
+
import { readDiagnosticEnv, withDiagnosticEnv } from "../runtime/diagnostic-env.js";
|
|
2
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
3
5
|
import { existsSync } from "node:fs";
|
|
4
6
|
import { createRequire } from "node:module";
|
|
5
7
|
import { join } from "node:path";
|
|
@@ -8,15 +10,15 @@ import { Hono } from "hono";
|
|
|
8
10
|
import { z } from "zod";
|
|
9
11
|
import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
|
|
10
12
|
import { validateFailClosedDeclaration } from "../declaration-validation.js";
|
|
11
|
-
import { safeProviderErrorObservability } from "../error-observability.js";
|
|
12
13
|
import {
|
|
13
14
|
createInProcessProviderEngine,
|
|
14
15
|
ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES,
|
|
15
16
|
isEngineOwnedEnvName,
|
|
16
|
-
readEngineProxyCredentials,
|
|
17
17
|
type ProviderEngine,
|
|
18
18
|
type ProviderEngineBindingCandidates,
|
|
19
|
+
readEngineProxyCredentials,
|
|
19
20
|
} from "../engine.js";
|
|
21
|
+
import { safeProviderErrorObservability } from "../error-observability.js";
|
|
20
22
|
import {
|
|
21
23
|
SDK_OWNED_PROVIDER_ERROR_CODES,
|
|
22
24
|
SDK_RUNTIME_OWNED_ERROR_CODES,
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
isTransportError,
|
|
30
32
|
isValidationError,
|
|
31
33
|
ProviderError,
|
|
34
|
+
SDKError,
|
|
32
35
|
type ProviderErrorObservability,
|
|
33
36
|
type ProviderErrorOptions,
|
|
34
37
|
} from "../errors.js";
|
|
@@ -41,11 +44,11 @@ import {
|
|
|
41
44
|
import type { ProviderLocale } from "../i18n/keys.js";
|
|
42
45
|
import {
|
|
43
46
|
categoryForStatus,
|
|
44
|
-
type ProviderErrorSource,
|
|
45
|
-
sourceForCategory,
|
|
46
47
|
isRetryableCategory,
|
|
47
48
|
PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
48
49
|
type ProviderErrorCategory,
|
|
50
|
+
type ProviderErrorSource,
|
|
51
|
+
sourceForCategory,
|
|
49
52
|
} from "../observability.js";
|
|
50
53
|
import { createScratchpad } from "../runtime/auth-flow.js";
|
|
51
54
|
import type * as BrowserRuntimeModule from "../runtime/browser.js";
|
|
@@ -55,13 +58,30 @@ import {
|
|
|
55
58
|
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
56
59
|
} from "../runtime/choice.js";
|
|
57
60
|
import { createCredentialContext } from "../runtime/credential.js";
|
|
61
|
+
import {
|
|
62
|
+
bindDiagnosticSensitiveRegistry,
|
|
63
|
+
type CompiledDiagnosticSensitiveValues,
|
|
64
|
+
compileProcessDiagnosticSensitiveValues,
|
|
65
|
+
createDiagnosticRedactor,
|
|
66
|
+
type DiagnosticRedactor,
|
|
67
|
+
REDACTION_FAILED,
|
|
68
|
+
isDiagnosticRedactionSuppressed,
|
|
69
|
+
bindCriticalDiagnosticRedactor,
|
|
70
|
+
redactCriticalDiagnosticText,
|
|
71
|
+
redactDiagnosticText,
|
|
72
|
+
registerDiagnosticSensitiveValues,
|
|
73
|
+
} from "../runtime/diagnostic-redactor.js";
|
|
74
|
+
import {
|
|
75
|
+
createCeremonyEgressLeaseRuntime,
|
|
76
|
+
ENGINE_CEREMONY_EGRESS_LEASE,
|
|
77
|
+
} from "../runtime/egress-lease.js";
|
|
58
78
|
import { createEnvContext } from "../runtime/env.js";
|
|
59
79
|
import { executeOperation } from "../runtime/executor.js";
|
|
60
80
|
import { createHttpClient } from "../runtime/http.js";
|
|
61
81
|
import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
|
|
62
82
|
import type * as NativeNetworkRuntimeModule from "../runtime/native-network.js";
|
|
63
|
-
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
64
83
|
import { createOcrClientFromEnv } from "../runtime/ocr.js";
|
|
84
|
+
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
65
85
|
import {
|
|
66
86
|
PROXY_AUTH_IP_DENIED_CODE,
|
|
67
87
|
PROXY_EDGE_AUTH_REJECTED_CODE,
|
|
@@ -73,18 +93,24 @@ import {
|
|
|
73
93
|
type ProxyTelemetryLogPayload,
|
|
74
94
|
} from "../runtime/proxy-telemetry.js";
|
|
75
95
|
import {
|
|
96
|
+
type ClosedEnum,
|
|
76
97
|
closedEnum,
|
|
77
98
|
RequestTelemetry,
|
|
78
|
-
tenantOpaqueKeys,
|
|
79
|
-
type ClosedEnum,
|
|
80
99
|
type TenantNeutral,
|
|
81
100
|
type TenantOpaqueKeys,
|
|
101
|
+
tenantOpaqueKeys,
|
|
82
102
|
} from "../runtime/request-telemetry.js";
|
|
83
103
|
import type * as ResolverRuntimeModule from "../runtime/resolver.js";
|
|
84
104
|
import {
|
|
105
|
+
bindResolverTelemetry,
|
|
85
106
|
createUnsupportedResolverClient,
|
|
107
|
+
inheritResolverTelemetryBinding,
|
|
86
108
|
type ResolverSolveWithRecorder,
|
|
87
109
|
} from "../runtime/resolver-shared.js";
|
|
110
|
+
import {
|
|
111
|
+
ResolverTelemetryCollector,
|
|
112
|
+
type ResolverTelemetryLogPayload,
|
|
113
|
+
} from "../runtime/resolver-telemetry.js";
|
|
88
114
|
import {
|
|
89
115
|
assertRequiredSecretsPresent,
|
|
90
116
|
listMissingRequiredSecrets,
|
|
@@ -94,32 +120,34 @@ import {
|
|
|
94
120
|
createProviderRuntimeStateFromEnv,
|
|
95
121
|
createUnsupportedProviderRuntimeState,
|
|
96
122
|
} from "../runtime/state.js";
|
|
97
|
-
import { StealthCookieJar } from "../runtime/stealth-cookies.js";
|
|
98
123
|
import type * as StealthRuntimeModule from "../runtime/stealth.js";
|
|
124
|
+
import type { StealthChallengeRuntime } from "../runtime/stealth-akamai-sbsd.js";
|
|
125
|
+
import { StealthCookieJar } from "../runtime/stealth-cookies.js";
|
|
99
126
|
import { createSttClientFromEnv } from "../runtime/stt.js";
|
|
100
127
|
import {
|
|
101
128
|
createTraceContext,
|
|
129
|
+
getTraceRecorder,
|
|
102
130
|
type TraceContext as RuntimeTraceContext,
|
|
103
131
|
type TraceRecorder,
|
|
104
132
|
updateTraceContextExportMetadata,
|
|
105
133
|
} from "../runtime/trace.js";
|
|
106
134
|
import { resolveTraceConfigFromEnv } from "../runtime/trace-config.js";
|
|
107
135
|
import { parseSchema } from "../schema.js";
|
|
136
|
+
import { StatefulRoutingDeadlineError } from "../stateful/errors.js";
|
|
108
137
|
import {
|
|
109
138
|
STATEFUL_NONCE_HEADER as STATEFUL_FORWARDING_NONCE_HEADER,
|
|
110
139
|
STATEFUL_SIGNATURE_HEADER as STATEFUL_FORWARDING_SIGNATURE_HEADER,
|
|
111
140
|
STATEFUL_TIMESTAMP_HEADER as STATEFUL_FORWARDING_TIMESTAMP_HEADER,
|
|
112
141
|
verifyStatefulRequestSignature,
|
|
113
142
|
} from "../stateful-signing.js";
|
|
114
|
-
import { StatefulRoutingDeadlineError } from "../stateful/errors.js";
|
|
115
143
|
import { getStealthProfile } from "../stealth/profiles.js";
|
|
116
|
-
import { sanitizeTraceAttributes } from "../trace-sanitization.js";
|
|
117
144
|
import {
|
|
118
145
|
APIFUSE_STREAM_DONE_EVENT,
|
|
119
146
|
APIFUSE_STREAM_ERROR_EVENT,
|
|
120
147
|
encodeSseEvent,
|
|
121
148
|
error as streamError,
|
|
122
149
|
} from "../stream.js";
|
|
150
|
+
import { sanitizeSpanNameForOutput, sanitizeTraceAttributes } from "../trace-sanitization.js";
|
|
123
151
|
import type {
|
|
124
152
|
AuthContext,
|
|
125
153
|
AuthTurn,
|
|
@@ -127,15 +155,15 @@ import type {
|
|
|
127
155
|
FlowContext,
|
|
128
156
|
FlowContextStore,
|
|
129
157
|
HttpRetrySummary,
|
|
158
|
+
OcrContext,
|
|
130
159
|
OperationDefinition,
|
|
131
160
|
OperationErrorCode,
|
|
132
161
|
OperationHttpStreamTransport,
|
|
133
162
|
OperationSseTransport,
|
|
134
|
-
OcrContext,
|
|
135
|
-
ProviderErrorStatus,
|
|
136
|
-
ProviderContext,
|
|
137
163
|
ProviderCacheResponseMeta,
|
|
164
|
+
ProviderContext,
|
|
138
165
|
ProviderDefinition,
|
|
166
|
+
ProviderErrorStatus,
|
|
139
167
|
ProviderFilesContext,
|
|
140
168
|
ProviderProxyPolicy,
|
|
141
169
|
ProviderRuntimeState,
|
|
@@ -149,6 +177,10 @@ import type {
|
|
|
149
177
|
import { VALID_OPERATION_ERROR_STATUSES } from "../types.js";
|
|
150
178
|
import type { SelfTestCancellationLogEvent } from "./self-test.js";
|
|
151
179
|
import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
|
|
180
|
+
import {
|
|
181
|
+
collectStaticDiagnosticSensitiveValues,
|
|
182
|
+
createDiagnosticEnvObserver,
|
|
183
|
+
} from "./sensitive-values.js";
|
|
152
184
|
import { resolveServerTraceContextOptions } from "./trace-output.js";
|
|
153
185
|
import {
|
|
154
186
|
type AuthFlowRequest,
|
|
@@ -294,9 +326,7 @@ export type ProviderServerOperationExecutorInput<
|
|
|
294
326
|
|
|
295
327
|
export type ProviderServerOperationExecutor<
|
|
296
328
|
TContext extends Partial<ProviderContext> = ProviderContext,
|
|
297
|
-
> = (
|
|
298
|
-
input: ProviderServerOperationExecutorInput<TContext>,
|
|
299
|
-
) => Promise<unknown>;
|
|
329
|
+
> = (input: ProviderServerOperationExecutorInput<TContext>) => Promise<unknown>;
|
|
300
330
|
|
|
301
331
|
type RequestTerminalOutcome =
|
|
302
332
|
| { kind: "completed"; status: number }
|
|
@@ -304,6 +334,7 @@ type RequestTerminalOutcome =
|
|
|
304
334
|
| { kind: "cancelled"; status?: number };
|
|
305
335
|
|
|
306
336
|
type RequestStreamLifecycle = {
|
|
337
|
+
redact: DiagnosticRedactor;
|
|
307
338
|
runStep<T>(fn: () => Promise<T>): Promise<T>;
|
|
308
339
|
registerCleanup(cleanup: () => void | Promise<void>): void;
|
|
309
340
|
terminalize(outcome: RequestTerminalOutcome): void;
|
|
@@ -513,6 +544,9 @@ function createLazyStealthClient(
|
|
|
513
544
|
async fetch(...args) {
|
|
514
545
|
return (await getSession()).fetch(...args);
|
|
515
546
|
},
|
|
547
|
+
async replayChallenged(...args) {
|
|
548
|
+
return (await getSession()).replayChallenged(...args);
|
|
549
|
+
},
|
|
516
550
|
redirects: {
|
|
517
551
|
async run(...args) {
|
|
518
552
|
return (await getSession()).redirects.run(...args);
|
|
@@ -553,7 +587,7 @@ function bindResolverSignalWithoutRuntime(
|
|
|
553
587
|
defaultSignal: AbortSignal | undefined,
|
|
554
588
|
): ResolverContext {
|
|
555
589
|
if (!defaultSignal) return resolver;
|
|
556
|
-
|
|
590
|
+
const boundResolver: ResolverContext = {
|
|
557
591
|
solve(challenge, signal = defaultSignal, traceRecorder?: TraceRecorder) {
|
|
558
592
|
// Forward the instrumentation trace recorder so resolver.vendor.* spans
|
|
559
593
|
// survive this wrapper. See ResolverSolveWithRecorder in resolver-shared.ts.
|
|
@@ -564,6 +598,8 @@ function bindResolverSignalWithoutRuntime(
|
|
|
564
598
|
);
|
|
565
599
|
},
|
|
566
600
|
};
|
|
601
|
+
inheritResolverTelemetryBinding(resolver, boundResolver);
|
|
602
|
+
return boundResolver;
|
|
567
603
|
}
|
|
568
604
|
|
|
569
605
|
function getProviderStealthBaseUrl(provider: ProviderDefinition): string | undefined {
|
|
@@ -579,6 +615,99 @@ function getProviderStealthProfile(provider: ProviderDefinition) {
|
|
|
579
615
|
return provider.stealth ? getStealthProfile(provider.stealth) : undefined;
|
|
580
616
|
}
|
|
581
617
|
|
|
618
|
+
type ResolverRuntimeOptions = ResolverRuntimeModule.ResolverRuntimeOptions;
|
|
619
|
+
|
|
620
|
+
/** One option set for ctx.resolver and the automatic SBSD solve, so both share identity scope, cache, proxy intent, and telemetry. */
|
|
621
|
+
function createResolverRuntimeOptions(
|
|
622
|
+
provider: ProviderDefinition,
|
|
623
|
+
cache: ReturnType<typeof createProviderCache>,
|
|
624
|
+
identityScope: string,
|
|
625
|
+
proxyPolicy: ProviderProxyPolicy | undefined,
|
|
626
|
+
proxyClientOptions: Omit<
|
|
627
|
+
NonNullable<ResolverRuntimeOptions["proxyIntent"]>,
|
|
628
|
+
"mode" | "userAgent"
|
|
629
|
+
>,
|
|
630
|
+
stealthProfile: { readonly userAgent: string } | undefined,
|
|
631
|
+
telemetry: ResolverTelemetryCollector,
|
|
632
|
+
): ResolverRuntimeOptions {
|
|
633
|
+
return {
|
|
634
|
+
allowedHosts: provider.allowedHosts,
|
|
635
|
+
cache,
|
|
636
|
+
telemetry,
|
|
637
|
+
identityScope,
|
|
638
|
+
...(proxyPolicy
|
|
639
|
+
? {
|
|
640
|
+
proxyIntent: {
|
|
641
|
+
mode: proxyPolicy.mode,
|
|
642
|
+
...proxyClientOptions,
|
|
643
|
+
...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
|
|
644
|
+
},
|
|
645
|
+
}
|
|
646
|
+
: {}),
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** Shared predicate for the SBSD challenge runtime and the ceremony egress lease. */
|
|
651
|
+
function declaresAkamaiSbsdResolver(provider: ProviderDefinition): boolean {
|
|
652
|
+
return provider.resolver?.kinds.some((kind) => kind === "akamai_sbsd") === true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function createStealthChallengeDetection(
|
|
656
|
+
provider: ProviderDefinition,
|
|
657
|
+
resolverRuntime: typeof ResolverRuntimeModule | undefined,
|
|
658
|
+
resolverOverride: ResolverContext | undefined,
|
|
659
|
+
resolverOptions: ResolverRuntimeOptions,
|
|
660
|
+
trace: RuntimeTraceContext,
|
|
661
|
+
signal: AbortSignal | undefined,
|
|
662
|
+
): StealthChallengeRuntime | undefined {
|
|
663
|
+
const resolverDeclared = declaresAkamaiSbsdResolver(provider);
|
|
664
|
+
const detectOnly = provider.stealth?.challengeDetection?.akamaiSbsd === true;
|
|
665
|
+
if (!resolverDeclared && !detectOnly) {
|
|
666
|
+
return undefined;
|
|
667
|
+
}
|
|
668
|
+
return {
|
|
669
|
+
akamaiSbsd: {
|
|
670
|
+
allowedHosts: provider.allowedHosts ?? [],
|
|
671
|
+
// The profile belongs to the SBSD resolver; a resolver declared for other kinds
|
|
672
|
+
// may name a different browser without affecting detect-only classification.
|
|
673
|
+
...(resolverDeclared && provider.resolver?.clientProfile
|
|
674
|
+
? { clientProfile: provider.resolver.clientProfile }
|
|
675
|
+
: {}),
|
|
676
|
+
...(resolverDeclared && resolverRuntime
|
|
677
|
+
? {
|
|
678
|
+
async solve(challenge, transport, solveSignal) {
|
|
679
|
+
const resolver = resolverRuntime.bindResolverSignal(
|
|
680
|
+
(resolverOverride
|
|
681
|
+
? bindResolverTelemetry(resolverOverride, resolverOptions.telemetry)
|
|
682
|
+
: undefined) ??
|
|
683
|
+
resolverRuntime.createResolverClientFromEnv(provider.resolver, undefined, {
|
|
684
|
+
allowedHosts: resolverOptions.allowedHosts,
|
|
685
|
+
cache: resolverOptions.cache,
|
|
686
|
+
telemetry: resolverOptions.telemetry,
|
|
687
|
+
identityScope: resolverOptions.identityScope,
|
|
688
|
+
// No proxyIntent: the transport is already bound to the initiating
|
|
689
|
+
// request's lease, and a second identity resolution would allocate
|
|
690
|
+
// again (and fail closed without allocator credentials) for nothing
|
|
691
|
+
// the SBSD adapters consume.
|
|
692
|
+
createTransport: () => transport,
|
|
693
|
+
}),
|
|
694
|
+
signal,
|
|
695
|
+
) as ResolverContext & Partial<ResolverSolveWithRecorder>;
|
|
696
|
+
// Same span and recorder threading as the instrumented ctx.resolver.solve.
|
|
697
|
+
const recorder = getTraceRecorder(trace);
|
|
698
|
+
const solve = () => resolver.solve(challenge, solveSignal, recorder ?? undefined);
|
|
699
|
+
return recorder
|
|
700
|
+
? recorder.runSpan("resolver.solve", solve, {
|
|
701
|
+
attributes: { challenge_kind: challenge.kind },
|
|
702
|
+
})
|
|
703
|
+
: solve();
|
|
704
|
+
},
|
|
705
|
+
}
|
|
706
|
+
: {}),
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
582
711
|
function isProductionProviderBrowserMode(provider: ProviderDefinition, env = process.env): boolean {
|
|
583
712
|
if (provider.runtime !== "browser") {
|
|
584
713
|
return false;
|
|
@@ -712,8 +841,10 @@ function providerSecretNames(provider: ProviderDefinition): string[] {
|
|
|
712
841
|
}
|
|
713
842
|
|
|
714
843
|
type RequestScopeContext = {
|
|
844
|
+
redact: DiagnosticRedactor;
|
|
715
845
|
trace: RuntimeTraceContext;
|
|
716
846
|
telemetry: RequestTelemetry;
|
|
847
|
+
resolverTelemetry: ResolverTelemetryCollector;
|
|
717
848
|
};
|
|
718
849
|
|
|
719
850
|
function createProviderContext(
|
|
@@ -741,16 +872,43 @@ function createProviderContext(
|
|
|
741
872
|
proxyClientOptions.affinityKey,
|
|
742
873
|
request.requestId,
|
|
743
874
|
);
|
|
875
|
+
const cache = createProviderCache({ providerId: provider.id });
|
|
744
876
|
let wrappedContext: ProviderContext | undefined;
|
|
877
|
+
const { capabilityModules } = options;
|
|
878
|
+
const resolverOptions = createResolverRuntimeOptions(
|
|
879
|
+
provider,
|
|
880
|
+
cache,
|
|
881
|
+
resolverIdentityScope,
|
|
882
|
+
proxyPolicy,
|
|
883
|
+
proxyClientOptions,
|
|
884
|
+
stealthProfile,
|
|
885
|
+
scope.resolverTelemetry,
|
|
886
|
+
);
|
|
887
|
+
const challengeRuntime = createStealthChallengeDetection(
|
|
888
|
+
provider,
|
|
889
|
+
capabilityModules.resolver,
|
|
890
|
+
options.resolver,
|
|
891
|
+
resolverOptions,
|
|
892
|
+
scope.trace,
|
|
893
|
+
signal,
|
|
894
|
+
);
|
|
745
895
|
const stealthClientOptions = {
|
|
746
896
|
upstream: proxyClientOptions.upstream,
|
|
747
897
|
affinityKey: proxyClientOptions.affinityKey,
|
|
748
898
|
telemetry: scope.telemetry.proxy,
|
|
749
899
|
engineCredentials: engineProxyCredentials,
|
|
750
900
|
...(signal ? { signal } : {}),
|
|
751
|
-
|
|
901
|
+
// A declared resolver without a `stealth` block still gets the default Chrome client
|
|
902
|
+
// (auth FlowContext is not capability-gated), so the runtime must ride along.
|
|
903
|
+
...(provider.stealth || challengeRuntime
|
|
904
|
+
? {
|
|
905
|
+
stealth: {
|
|
906
|
+
...(provider.stealth ?? {}),
|
|
907
|
+
...(challengeRuntime ? { challengeRuntime } : {}),
|
|
908
|
+
},
|
|
909
|
+
}
|
|
910
|
+
: {}),
|
|
752
911
|
};
|
|
753
|
-
const { capabilityModules } = options;
|
|
754
912
|
const logStealthCleanupError = (error: unknown) =>
|
|
755
913
|
logProviderCleanupError(
|
|
756
914
|
options.logger,
|
|
@@ -760,6 +918,7 @@ function createProviderContext(
|
|
|
760
918
|
request.requestId,
|
|
761
919
|
"stealth",
|
|
762
920
|
error,
|
|
921
|
+
scope.redact,
|
|
763
922
|
);
|
|
764
923
|
|
|
765
924
|
const env = createEnvContext([
|
|
@@ -777,7 +936,6 @@ function createProviderContext(
|
|
|
777
936
|
headers: request.headers ?? {},
|
|
778
937
|
};
|
|
779
938
|
const requestState = state.forConnection(requestContext.connectionId);
|
|
780
|
-
const cache = createProviderCache({ providerId: provider.id });
|
|
781
939
|
const bindings: ProviderEngineBindingCandidates = {
|
|
782
940
|
env,
|
|
783
941
|
credential,
|
|
@@ -802,7 +960,7 @@ function createProviderContext(
|
|
|
802
960
|
provider.runtime === "browser"
|
|
803
961
|
? capabilityModules.browser!.createBrowserClient({
|
|
804
962
|
allowedHosts: provider.allowedHosts,
|
|
805
|
-
cdpUrl:
|
|
963
|
+
cdpUrl: readDiagnosticEnv("APIFUSE__CDP_POOL__URL"),
|
|
806
964
|
headless: true,
|
|
807
965
|
requireCdpPool: isProductionProviderBrowserMode(provider),
|
|
808
966
|
stealth: true,
|
|
@@ -829,25 +987,20 @@ function createProviderContext(
|
|
|
829
987
|
stt: options.stt ?? createSttClientFromEnv(provider.stt),
|
|
830
988
|
resolver: capabilityModules.resolver
|
|
831
989
|
? capabilityModules.resolver.bindResolverSignal(
|
|
832
|
-
options.resolver
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
mode: proxyPolicy.mode,
|
|
841
|
-
...proxyClientOptions,
|
|
842
|
-
...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
|
|
843
|
-
},
|
|
844
|
-
}
|
|
845
|
-
: {}),
|
|
846
|
-
}),
|
|
990
|
+
(options.resolver
|
|
991
|
+
? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
|
|
992
|
+
: undefined) ??
|
|
993
|
+
capabilityModules.resolver.createResolverClientFromEnv(
|
|
994
|
+
provider.resolver,
|
|
995
|
+
undefined,
|
|
996
|
+
resolverOptions,
|
|
997
|
+
),
|
|
847
998
|
signal,
|
|
848
999
|
)
|
|
849
1000
|
: bindResolverSignalWithoutRuntime(
|
|
850
|
-
options.resolver
|
|
1001
|
+
(options.resolver
|
|
1002
|
+
? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
|
|
1003
|
+
: undefined) ??
|
|
851
1004
|
createUnsupportedResolverClient("Provider does not declare resolver capability"),
|
|
852
1005
|
signal,
|
|
853
1006
|
),
|
|
@@ -927,6 +1080,7 @@ export function resolveAuthFlowProxyAffinityKey(
|
|
|
927
1080
|
function createAuthFlowContext(
|
|
928
1081
|
provider: ProviderDefinition,
|
|
929
1082
|
request: AuthFlowRequest,
|
|
1083
|
+
route: AuthRoute,
|
|
930
1084
|
options: ProviderServerRuntimeOptions,
|
|
931
1085
|
state: ProviderRuntimeState,
|
|
932
1086
|
scope: RequestScopeContext,
|
|
@@ -934,6 +1088,7 @@ function createAuthFlowContext(
|
|
|
934
1088
|
): {
|
|
935
1089
|
context: FlowContext;
|
|
936
1090
|
getPatch: () => Record<string, unknown | null> | undefined;
|
|
1091
|
+
getEngineState: () => AuthFlowSuccessResponse["engine"];
|
|
937
1092
|
} {
|
|
938
1093
|
const baseUrl = getProviderBaseUrl(provider);
|
|
939
1094
|
const stealthBaseUrl = getProviderStealthBaseUrl(provider);
|
|
@@ -951,20 +1106,73 @@ function createAuthFlowContext(
|
|
|
951
1106
|
telemetry: scope.telemetry.proxy,
|
|
952
1107
|
engineCredentials: engineProxyCredentials,
|
|
953
1108
|
};
|
|
1109
|
+
// The lease pins the proxy egress a challenged ceremony solved on, so only providers
|
|
1110
|
+
// that declare the akamai_sbsd resolver kind carry one (same predicate as the challenge
|
|
1111
|
+
// runtime). `abort` never evaluates the key or tenant: a user must be able to cancel a
|
|
1112
|
+
// stranded flow even when engine provisioning is broken, and an abort is not a
|
|
1113
|
+
// continuation of the ceremony the lease pins.
|
|
1114
|
+
const ceremonyEgressLease =
|
|
1115
|
+
route !== "abort" &&
|
|
1116
|
+
declaresAkamaiSbsdResolver(provider) &&
|
|
1117
|
+
proxyPolicy &&
|
|
1118
|
+
proxyPolicy.mode !== "disabled"
|
|
1119
|
+
? createCeremonyEgressLeaseRuntime({
|
|
1120
|
+
tenantId: request.tenantId,
|
|
1121
|
+
providerId: provider.id,
|
|
1122
|
+
flowId: request.flowId,
|
|
1123
|
+
affinityKey: proxyClientOptions.affinityKey,
|
|
1124
|
+
onHandle: (handle) => registerDiagnosticSensitiveValues(scope.trace, [handle]),
|
|
1125
|
+
...(request.engine?.egressLease ? { handle: request.engine.egressLease } : {}),
|
|
1126
|
+
})
|
|
1127
|
+
: undefined;
|
|
1128
|
+
if (route !== "abort" && request.engine?.egressLease && !ceremonyEgressLease) {
|
|
1129
|
+
throw new SDKError(
|
|
1130
|
+
"An egress lease was supplied for an auth flow that does not use a ceremony lease",
|
|
1131
|
+
{ code: "EGRESS_LEASE_INVALID" },
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
954
1134
|
const resolverIdentityScope = resolveProviderResolverIdentityScope(
|
|
955
1135
|
provider,
|
|
956
1136
|
proxyClientOptions.affinityKey,
|
|
957
1137
|
request.requestId,
|
|
958
1138
|
);
|
|
1139
|
+
const cache = createProviderCache({ providerId: provider.id });
|
|
1140
|
+
const { capabilityModules } = options;
|
|
1141
|
+
const resolverOptions = createResolverRuntimeOptions(
|
|
1142
|
+
provider,
|
|
1143
|
+
cache,
|
|
1144
|
+
resolverIdentityScope,
|
|
1145
|
+
proxyPolicy,
|
|
1146
|
+
proxyClientOptions,
|
|
1147
|
+
stealthProfile,
|
|
1148
|
+
scope.resolverTelemetry,
|
|
1149
|
+
);
|
|
1150
|
+
const challengeRuntime = createStealthChallengeDetection(
|
|
1151
|
+
provider,
|
|
1152
|
+
capabilityModules.resolver,
|
|
1153
|
+
options.resolver,
|
|
1154
|
+
resolverOptions,
|
|
1155
|
+
scope.trace,
|
|
1156
|
+
signal,
|
|
1157
|
+
);
|
|
959
1158
|
const stealthClientOptions = {
|
|
960
1159
|
upstream: proxyClientOptions.upstream,
|
|
961
1160
|
affinityKey: proxyClientOptions.affinityKey,
|
|
962
1161
|
telemetry: scope.telemetry.proxy,
|
|
963
1162
|
engineCredentials: engineProxyCredentials,
|
|
1163
|
+
...(ceremonyEgressLease ? { [ENGINE_CEREMONY_EGRESS_LEASE]: ceremonyEgressLease } : {}),
|
|
964
1164
|
...(signal ? { signal } : {}),
|
|
965
|
-
|
|
1165
|
+
// A declared resolver without a `stealth` block still gets the default Chrome client
|
|
1166
|
+
// (auth FlowContext is not capability-gated), so the runtime must ride along.
|
|
1167
|
+
...(provider.stealth || challengeRuntime
|
|
1168
|
+
? {
|
|
1169
|
+
stealth: {
|
|
1170
|
+
...(provider.stealth ?? {}),
|
|
1171
|
+
...(challengeRuntime ? { challengeRuntime } : {}),
|
|
1172
|
+
},
|
|
1173
|
+
}
|
|
1174
|
+
: {}),
|
|
966
1175
|
};
|
|
967
|
-
const { capabilityModules } = options;
|
|
968
1176
|
const logStealthCleanupError = (error: unknown) =>
|
|
969
1177
|
logProviderCleanupError(
|
|
970
1178
|
options.logger,
|
|
@@ -974,6 +1182,7 @@ function createAuthFlowContext(
|
|
|
974
1182
|
request.requestId,
|
|
975
1183
|
"stealth",
|
|
976
1184
|
error,
|
|
1185
|
+
scope.redact,
|
|
977
1186
|
);
|
|
978
1187
|
const credential = request.connection
|
|
979
1188
|
? createCredentialContext({
|
|
@@ -983,8 +1192,6 @@ function createAuthFlowContext(
|
|
|
983
1192
|
values: request.connection.secrets,
|
|
984
1193
|
})
|
|
985
1194
|
: undefined;
|
|
986
|
-
const cache = createProviderCache({ providerId: provider.id });
|
|
987
|
-
|
|
988
1195
|
const context: FlowContext = wrapWithInstrumentation({
|
|
989
1196
|
flowId: request.flowId,
|
|
990
1197
|
connectionId: resolveOperationConnectionId(request),
|
|
@@ -1026,25 +1233,20 @@ function createAuthFlowContext(
|
|
|
1026
1233
|
stt: options.stt ?? createSttClientFromEnv(provider.stt),
|
|
1027
1234
|
resolver: capabilityModules.resolver
|
|
1028
1235
|
? capabilityModules.resolver.bindResolverSignal(
|
|
1029
|
-
options.resolver
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
mode: proxyPolicy.mode,
|
|
1038
|
-
...proxyClientOptions,
|
|
1039
|
-
...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
|
|
1040
|
-
},
|
|
1041
|
-
}
|
|
1042
|
-
: {}),
|
|
1043
|
-
}),
|
|
1236
|
+
(options.resolver
|
|
1237
|
+
? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
|
|
1238
|
+
: undefined) ??
|
|
1239
|
+
capabilityModules.resolver.createResolverClientFromEnv(
|
|
1240
|
+
provider.resolver,
|
|
1241
|
+
undefined,
|
|
1242
|
+
resolverOptions,
|
|
1243
|
+
),
|
|
1044
1244
|
signal,
|
|
1045
1245
|
)
|
|
1046
1246
|
: bindResolverSignalWithoutRuntime(
|
|
1047
|
-
options.resolver
|
|
1247
|
+
(options.resolver
|
|
1248
|
+
? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
|
|
1249
|
+
: undefined) ??
|
|
1048
1250
|
createUnsupportedResolverClient("Provider does not declare resolver capability"),
|
|
1049
1251
|
signal,
|
|
1050
1252
|
),
|
|
@@ -1054,6 +1256,10 @@ function createAuthFlowContext(
|
|
|
1054
1256
|
return {
|
|
1055
1257
|
context,
|
|
1056
1258
|
getPatch: flowContextStore.getPatch,
|
|
1259
|
+
getEngineState() {
|
|
1260
|
+
const egressLease = ceremonyEgressLease?.handle();
|
|
1261
|
+
return egressLease ? { egressLease } : undefined;
|
|
1262
|
+
},
|
|
1057
1263
|
};
|
|
1058
1264
|
}
|
|
1059
1265
|
|
|
@@ -1082,6 +1288,7 @@ type ProviderServerLogEventBase = ProviderRequestCost & {
|
|
|
1082
1288
|
requestedProviderId?: string;
|
|
1083
1289
|
status: number;
|
|
1084
1290
|
proxy?: ProxyTelemetryLogPayload;
|
|
1291
|
+
resolver?: ResolverTelemetryLogPayload;
|
|
1085
1292
|
};
|
|
1086
1293
|
|
|
1087
1294
|
export type ProviderServerLogEvent =
|
|
@@ -1167,7 +1374,11 @@ export type ProviderServerOptions<TContext extends Partial<ProviderContext> = Pr
|
|
|
1167
1374
|
stt?: SttContext;
|
|
1168
1375
|
/** Optional OCR override for tests or custom hosts; local/prod normally resolves from env. */
|
|
1169
1376
|
ocr?: OcrContext;
|
|
1170
|
-
/**
|
|
1377
|
+
/**
|
|
1378
|
+
* Optional resolver override for tests or custom hosts; local/prod normally resolves from env.
|
|
1379
|
+
* SDK chains are reconstructed with each request's telemetry sink. An opaque host
|
|
1380
|
+
* resolver is reported as one custom invocation; its internal vendor work is not exposed.
|
|
1381
|
+
*/
|
|
1171
1382
|
resolver?: ResolverContext;
|
|
1172
1383
|
/** Optional runtime state override for tests or custom hosts. Production resolves Redis from env and fails closed when unavailable. */
|
|
1173
1384
|
state?: ProviderRuntimeState;
|
|
@@ -1229,15 +1440,18 @@ function finishRequestCost(input: {
|
|
|
1229
1440
|
};
|
|
1230
1441
|
}
|
|
1231
1442
|
|
|
1232
|
-
function zodDetails(
|
|
1443
|
+
function zodDetails(
|
|
1444
|
+
error: z.ZodError,
|
|
1445
|
+
redact?: DiagnosticRedactor,
|
|
1446
|
+
): Array<{
|
|
1233
1447
|
path: string;
|
|
1234
1448
|
code: string;
|
|
1235
1449
|
message: string;
|
|
1236
1450
|
}> {
|
|
1237
1451
|
return error.issues.map((issue) => ({
|
|
1238
|
-
path: issue.path.join("."),
|
|
1239
|
-
code: issue.code,
|
|
1240
|
-
message: issue.message,
|
|
1452
|
+
path: sanitizeSpanNameForOutput(issue.path.join("."), redact),
|
|
1453
|
+
code: sanitizeSpanNameForOutput(issue.code, redact),
|
|
1454
|
+
message: sanitizeSpanNameForOutput(issue.message, redact),
|
|
1241
1455
|
}));
|
|
1242
1456
|
}
|
|
1243
1457
|
|
|
@@ -1264,6 +1478,7 @@ function toErrorResponse(
|
|
|
1264
1478
|
error: unknown,
|
|
1265
1479
|
requestId: string | undefined,
|
|
1266
1480
|
observabilityDetails: ErrorObservabilityDetails,
|
|
1481
|
+
redact?: DiagnosticRedactor,
|
|
1267
1482
|
): OperationErrorResponse {
|
|
1268
1483
|
const observability = observabilityDetails;
|
|
1269
1484
|
const source = publicErrorSource(error, observability.category);
|
|
@@ -1302,7 +1517,7 @@ function toErrorResponse(
|
|
|
1302
1517
|
...(requestId ? { requestId } : {}),
|
|
1303
1518
|
retryable: observability.retryable,
|
|
1304
1519
|
source,
|
|
1305
|
-
details: zodDetails(error),
|
|
1520
|
+
details: zodDetails(error, redact),
|
|
1306
1521
|
},
|
|
1307
1522
|
};
|
|
1308
1523
|
}
|
|
@@ -1598,14 +1813,17 @@ export type ProviderErrorCauseFrame = {
|
|
|
1598
1813
|
const MAX_PROVIDER_ERROR_CAUSE_FRAMES = 5;
|
|
1599
1814
|
const MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH = 300;
|
|
1600
1815
|
|
|
1601
|
-
function providerErrorCauseMessage(message: string): string {
|
|
1602
|
-
const sanitized = sanitizeDiagnosticText(message);
|
|
1816
|
+
function providerErrorCauseMessage(message: string, redact?: DiagnosticRedactor): string {
|
|
1817
|
+
const sanitized = sanitizeDiagnosticText(redactDiagnosticText(message, redact));
|
|
1603
1818
|
return sanitized.length > MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH
|
|
1604
1819
|
? `${sanitized.slice(0, MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH)}… [truncated]`
|
|
1605
1820
|
: sanitized;
|
|
1606
1821
|
}
|
|
1607
1822
|
|
|
1608
|
-
function providerErrorCauseChain(
|
|
1823
|
+
function providerErrorCauseChain(
|
|
1824
|
+
error: unknown,
|
|
1825
|
+
redact?: DiagnosticRedactor,
|
|
1826
|
+
): ProviderErrorCauseFrame[] | undefined {
|
|
1609
1827
|
if (!(error instanceof Error) && !isProviderError(error)) return undefined;
|
|
1610
1828
|
|
|
1611
1829
|
const seen = new Set<object>([error]);
|
|
@@ -1622,9 +1840,9 @@ function providerErrorCauseChain(error: unknown): ProviderErrorCauseFrame[] | un
|
|
|
1622
1840
|
const providerObservability = safeProviderErrorObservability(cause);
|
|
1623
1841
|
const causeCode = providerErrorCode(cause);
|
|
1624
1842
|
frames.push({
|
|
1625
|
-
errorClass: cause.name,
|
|
1626
|
-
...(causeCode !== undefined ? { code: causeCode } : {}),
|
|
1627
|
-
message: providerErrorCauseMessage(message),
|
|
1843
|
+
errorClass: redactDiagnosticText(cause.name, redact),
|
|
1844
|
+
...(causeCode !== undefined ? { code: redactDiagnosticText(causeCode, redact) } : {}),
|
|
1845
|
+
message: providerErrorCauseMessage(message, redact),
|
|
1628
1846
|
messageLength: message.length,
|
|
1629
1847
|
messageFingerprint: createHash("sha256").update(message).digest("hex").slice(0, 12),
|
|
1630
1848
|
...(providerObservability ? { providerObservability } : {}),
|
|
@@ -1648,6 +1866,7 @@ function logProviderError(
|
|
|
1648
1866
|
telemetry: RequestTelemetry | undefined,
|
|
1649
1867
|
observabilityDetails: ErrorObservabilityDetails,
|
|
1650
1868
|
correlation: RequestCorrelationIds = {},
|
|
1869
|
+
redact?: DiagnosticRedactor,
|
|
1651
1870
|
): void {
|
|
1652
1871
|
const providerCode = isProviderError(error) ? providerErrorCode(error) : undefined;
|
|
1653
1872
|
const code = isProviderError(error)
|
|
@@ -1659,7 +1878,7 @@ function logProviderError(
|
|
|
1659
1878
|
: "internal_error";
|
|
1660
1879
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
1661
1880
|
const message = error instanceof Error ? error.message : String(error);
|
|
1662
|
-
const causeChain = providerErrorCauseChain(error);
|
|
1881
|
+
const causeChain = providerErrorCauseChain(error, redact);
|
|
1663
1882
|
const details = observabilityDetails;
|
|
1664
1883
|
const isUnregisteredProviderErrorCode =
|
|
1665
1884
|
status === 500 &&
|
|
@@ -1679,24 +1898,28 @@ function logProviderError(
|
|
|
1679
1898
|
emit({
|
|
1680
1899
|
level: status >= 500 ? "error" : "warn",
|
|
1681
1900
|
event: "provider_request_failed",
|
|
1682
|
-
providerId: provider.id,
|
|
1901
|
+
providerId: redactDiagnosticText(provider.id, redact),
|
|
1683
1902
|
kind,
|
|
1684
|
-
route,
|
|
1685
|
-
...(requestId ? { requestId } : {}),
|
|
1903
|
+
route: redactCriticalDiagnosticText(route, redact),
|
|
1904
|
+
...(requestId ? { requestId: redactCriticalDiagnosticText(requestId, redact) } : {}),
|
|
1686
1905
|
...(correlation.connectionId !== undefined
|
|
1687
|
-
? { connectionId: correlation.connectionId }
|
|
1906
|
+
? { connectionId: redactDiagnosticText(correlation.connectionId, redact) }
|
|
1907
|
+
: {}),
|
|
1908
|
+
...(correlation.flowId !== undefined
|
|
1909
|
+
? { flowId: redactDiagnosticText(correlation.flowId, redact) }
|
|
1910
|
+
: {}),
|
|
1911
|
+
...(correlation.tenantId !== undefined
|
|
1912
|
+
? { tenantId: redactDiagnosticText(correlation.tenantId, redact) }
|
|
1688
1913
|
: {}),
|
|
1689
|
-
...(correlation.flowId !== undefined ? { flowId: correlation.flowId } : {}),
|
|
1690
|
-
...(correlation.tenantId !== undefined ? { tenantId: correlation.tenantId } : {}),
|
|
1691
1914
|
...(correlation.requestedProviderId !== undefined
|
|
1692
|
-
? { requestedProviderId: correlation.requestedProviderId }
|
|
1915
|
+
? { requestedProviderId: redactDiagnosticText(correlation.requestedProviderId, redact) }
|
|
1693
1916
|
: {}),
|
|
1694
1917
|
status,
|
|
1695
1918
|
...cost,
|
|
1696
1919
|
...(telemetryPayload ?? {}),
|
|
1697
|
-
code,
|
|
1698
|
-
errorClass,
|
|
1699
|
-
message,
|
|
1920
|
+
code: sanitizeSpanNameForOutput(redactCriticalDiagnosticText(code, redact)),
|
|
1921
|
+
errorClass: redactDiagnosticText(errorClass, redact),
|
|
1922
|
+
message: sanitizeDiagnosticText(redactDiagnosticText(message, redact)),
|
|
1700
1923
|
...(causeChain ? { causeChain } : {}),
|
|
1701
1924
|
...(providerObservability ? { providerObservability } : {}),
|
|
1702
1925
|
...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
|
|
@@ -1710,7 +1933,7 @@ function logProviderError(
|
|
|
1710
1933
|
"Declare this code (with status and retryable) in the operation's errorCodes so it serves its intended status instead of 500.",
|
|
1711
1934
|
}
|
|
1712
1935
|
: {}),
|
|
1713
|
-
...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
|
|
1936
|
+
...(error instanceof z.ZodError ? { issues: zodDetails(error, redact) } : {}),
|
|
1714
1937
|
});
|
|
1715
1938
|
}
|
|
1716
1939
|
|
|
@@ -1722,6 +1945,7 @@ function logProviderCleanupError(
|
|
|
1722
1945
|
requestId: string | undefined,
|
|
1723
1946
|
resource: "browser" | "stealth",
|
|
1724
1947
|
error: unknown,
|
|
1948
|
+
redact?: DiagnosticRedactor,
|
|
1725
1949
|
): void {
|
|
1726
1950
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
1727
1951
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
@@ -1729,13 +1953,13 @@ function logProviderCleanupError(
|
|
|
1729
1953
|
emit({
|
|
1730
1954
|
level: "warn",
|
|
1731
1955
|
event: "provider_cleanup_failed",
|
|
1732
|
-
providerId: provider.id,
|
|
1956
|
+
providerId: redactDiagnosticText(provider.id, redact),
|
|
1733
1957
|
kind,
|
|
1734
|
-
route: operationId,
|
|
1735
|
-
...(requestId ? { requestId } : {}),
|
|
1958
|
+
route: redactCriticalDiagnosticText(operationId, redact),
|
|
1959
|
+
...(requestId ? { requestId: redactCriticalDiagnosticText(requestId, redact) } : {}),
|
|
1736
1960
|
resource,
|
|
1737
|
-
errorClass,
|
|
1738
|
-
message,
|
|
1961
|
+
errorClass: redactDiagnosticText(errorClass, redact),
|
|
1962
|
+
message: sanitizeDiagnosticText(redactDiagnosticText(message, redact)),
|
|
1739
1963
|
});
|
|
1740
1964
|
}
|
|
1741
1965
|
|
|
@@ -1749,23 +1973,28 @@ function logProviderSuccess(
|
|
|
1749
1973
|
cost: ProviderRequestCost,
|
|
1750
1974
|
telemetry?: RequestTelemetry,
|
|
1751
1975
|
correlation: RequestCorrelationIds = {},
|
|
1976
|
+
redact?: DiagnosticRedactor,
|
|
1752
1977
|
): void {
|
|
1753
1978
|
const telemetryPayload = telemetry?.toLogPayload();
|
|
1754
1979
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
1755
1980
|
emit({
|
|
1756
1981
|
level: "info",
|
|
1757
1982
|
event: "provider_request_completed",
|
|
1758
|
-
providerId: provider.id,
|
|
1983
|
+
providerId: redactDiagnosticText(provider.id, redact),
|
|
1759
1984
|
kind,
|
|
1760
|
-
route,
|
|
1761
|
-
...(requestId ? { requestId } : {}),
|
|
1985
|
+
route: redactCriticalDiagnosticText(route, redact),
|
|
1986
|
+
...(requestId ? { requestId: redactCriticalDiagnosticText(requestId, redact) } : {}),
|
|
1762
1987
|
...(correlation.connectionId !== undefined
|
|
1763
|
-
? { connectionId: correlation.connectionId }
|
|
1988
|
+
? { connectionId: redactDiagnosticText(correlation.connectionId, redact) }
|
|
1989
|
+
: {}),
|
|
1990
|
+
...(correlation.flowId !== undefined
|
|
1991
|
+
? { flowId: redactDiagnosticText(correlation.flowId, redact) }
|
|
1992
|
+
: {}),
|
|
1993
|
+
...(correlation.tenantId !== undefined
|
|
1994
|
+
? { tenantId: redactDiagnosticText(correlation.tenantId, redact) }
|
|
1764
1995
|
: {}),
|
|
1765
|
-
...(correlation.flowId !== undefined ? { flowId: correlation.flowId } : {}),
|
|
1766
|
-
...(correlation.tenantId !== undefined ? { tenantId: correlation.tenantId } : {}),
|
|
1767
1996
|
...(correlation.requestedProviderId !== undefined
|
|
1768
|
-
? { requestedProviderId: correlation.requestedProviderId }
|
|
1997
|
+
? { requestedProviderId: redactDiagnosticText(correlation.requestedProviderId, redact) }
|
|
1769
1998
|
: {}),
|
|
1770
1999
|
status,
|
|
1771
2000
|
...cost,
|
|
@@ -1779,6 +2008,7 @@ type RequestScopeFinishResult = {
|
|
|
1779
2008
|
};
|
|
1780
2009
|
|
|
1781
2010
|
type RequestScope = RequestScopeContext & {
|
|
2011
|
+
seedCredentials(rawBody: unknown, kind: "operation" | "auth" | "stateful"): void;
|
|
1782
2012
|
enrich(input: {
|
|
1783
2013
|
route?: string;
|
|
1784
2014
|
requestId?: string;
|
|
@@ -1850,8 +2080,91 @@ function requestTraceId(
|
|
|
1850
2080
|
);
|
|
1851
2081
|
}
|
|
1852
2082
|
|
|
2083
|
+
/** Harvest only credential subtrees, iteratively and before schema diagnostics. */
|
|
2084
|
+
function rawRequestCredentials(
|
|
2085
|
+
rawBody: unknown,
|
|
2086
|
+
kind: "operation" | "auth" | "stateful",
|
|
2087
|
+
): { values: string[]; exhausted: boolean } {
|
|
2088
|
+
const record = (value: unknown): Record<string, unknown> | undefined =>
|
|
2089
|
+
value !== null && typeof value === "object" ? (value as Record<string, unknown>) : undefined;
|
|
2090
|
+
const own = (value: unknown, key: string): unknown => {
|
|
2091
|
+
const object = record(value);
|
|
2092
|
+
return object && Object.hasOwn(object, key) ? object[key] : undefined;
|
|
2093
|
+
};
|
|
2094
|
+
const body = kind === "stateful" ? own(rawBody, "operationRequest") : rawBody;
|
|
2095
|
+
const values: string[] = [];
|
|
2096
|
+
let remainingBytes = 64 * 1024;
|
|
2097
|
+
let exhausted = false;
|
|
2098
|
+
const add = (value: unknown): void => {
|
|
2099
|
+
if (typeof value !== "string") return;
|
|
2100
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
2101
|
+
if (bytes > remainingBytes) {
|
|
2102
|
+
exhausted = true;
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
remainingBytes -= bytes;
|
|
2106
|
+
if (value) values.push(value);
|
|
2107
|
+
const trimmed = value.trim();
|
|
2108
|
+
if (trimmed && trimmed !== value) values.push(trimmed);
|
|
2109
|
+
};
|
|
2110
|
+
function* entries(object: Record<string, unknown>) {
|
|
2111
|
+
for (const key in object) {
|
|
2112
|
+
if (Object.hasOwn(object, key)) yield [key, object[key]] as const;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
const visit = (root: unknown): void => {
|
|
2116
|
+
const object = record(root);
|
|
2117
|
+
if (!object) {
|
|
2118
|
+
add(root);
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
// Connection inventories are usually flat. Walk the root directly and only
|
|
2122
|
+
// allocate iterator frames when a nested credential container is encountered.
|
|
2123
|
+
for (const key in object) {
|
|
2124
|
+
if (!Object.hasOwn(object, key)) continue;
|
|
2125
|
+
const value = object[key];
|
|
2126
|
+
add(key);
|
|
2127
|
+
if (exhausted) break;
|
|
2128
|
+
const child = record(value);
|
|
2129
|
+
if (!child) {
|
|
2130
|
+
add(value);
|
|
2131
|
+
if (exhausted) break;
|
|
2132
|
+
continue;
|
|
2133
|
+
}
|
|
2134
|
+
const stack = [{ iterator: entries(child), depth: 1 }];
|
|
2135
|
+
while (stack.length && !exhausted) {
|
|
2136
|
+
const frame = stack[stack.length - 1];
|
|
2137
|
+
const entry = frame.iterator.next();
|
|
2138
|
+
if (entry.done) {
|
|
2139
|
+
stack.pop();
|
|
2140
|
+
continue;
|
|
2141
|
+
}
|
|
2142
|
+
const [key, value] = entry.value;
|
|
2143
|
+
add(key);
|
|
2144
|
+
if (exhausted) break;
|
|
2145
|
+
const child = record(value);
|
|
2146
|
+
if (child) {
|
|
2147
|
+
if (frame.depth >= 8) {
|
|
2148
|
+
exhausted = true;
|
|
2149
|
+
break;
|
|
2150
|
+
}
|
|
2151
|
+
stack.push({ iterator: entries(child), depth: frame.depth + 1 });
|
|
2152
|
+
} else add(value);
|
|
2153
|
+
}
|
|
2154
|
+
if (exhausted) break;
|
|
2155
|
+
}
|
|
2156
|
+
};
|
|
2157
|
+
visit(own(own(body, "connection"), "secrets"));
|
|
2158
|
+
if (kind === "auth" && !exhausted) {
|
|
2159
|
+
add(own(own(body, "engine"), "egressLease"));
|
|
2160
|
+
if (!exhausted) visit(own(body, "input"));
|
|
2161
|
+
}
|
|
2162
|
+
return { values, exhausted };
|
|
2163
|
+
}
|
|
2164
|
+
|
|
1853
2165
|
function createRequestScope(input: {
|
|
1854
2166
|
provider: ProviderDefinition;
|
|
2167
|
+
staticSensitiveValues: CompiledDiagnosticSensitiveValues;
|
|
1855
2168
|
kind: "operation" | "auth";
|
|
1856
2169
|
route: string;
|
|
1857
2170
|
requestId?: string;
|
|
@@ -1863,8 +2176,25 @@ function createRequestScope(input: {
|
|
|
1863
2176
|
setHeader?: (name: string, value: string) => void;
|
|
1864
2177
|
declaredErrorCode?: (error: unknown) => OperationErrorCode | undefined;
|
|
1865
2178
|
}): RequestScope {
|
|
2179
|
+
const sensitiveRegistry = createDiagnosticRedactor([], input.staticSensitiveValues);
|
|
2180
|
+
const redact = (text: string) => redactDiagnosticText(text, sensitiveRegistry.redact);
|
|
2181
|
+
bindCriticalDiagnosticRedactor(
|
|
2182
|
+
redact,
|
|
2183
|
+
sensitiveRegistry.redactCritical,
|
|
2184
|
+
sensitiveRegistry.redactStructured,
|
|
2185
|
+
() => sensitiveRegistry.suppressed,
|
|
2186
|
+
);
|
|
1866
2187
|
const requestCost = startRequestCost();
|
|
1867
|
-
const
|
|
2188
|
+
const observeEnv = {
|
|
2189
|
+
observe: createDiagnosticEnvObserver(
|
|
2190
|
+
input.provider,
|
|
2191
|
+
sensitiveRegistry.add,
|
|
2192
|
+
sensitiveRegistry.has,
|
|
2193
|
+
),
|
|
2194
|
+
finished: false,
|
|
2195
|
+
staticValues: input.staticSensitiveValues,
|
|
2196
|
+
};
|
|
2197
|
+
const traceConfig = withDiagnosticEnv(observeEnv, () => resolveTraceConfigFromEnv());
|
|
1868
2198
|
const details = {
|
|
1869
2199
|
route: input.route,
|
|
1870
2200
|
requestId: input.requestId,
|
|
@@ -1883,13 +2213,19 @@ function createRequestScope(input: {
|
|
|
1883
2213
|
const initialTraceId = requestTraceId(input.headers, input.requestId);
|
|
1884
2214
|
const trace: RuntimeTraceContext = traceConfig
|
|
1885
2215
|
? createTraceContext({
|
|
1886
|
-
...
|
|
2216
|
+
...withDiagnosticEnv(observeEnv, () =>
|
|
2217
|
+
resolveServerTraceContextOptions(traceConfig, traceAttributes, undefined, redact),
|
|
2218
|
+
),
|
|
2219
|
+
redact,
|
|
1887
2220
|
...(initialTraceId ? { traceId: initialTraceId } : {}),
|
|
1888
2221
|
})
|
|
1889
|
-
: createTraceContext();
|
|
2222
|
+
: createTraceContext({ redact });
|
|
2223
|
+
bindDiagnosticSensitiveRegistry(trace, sensitiveRegistry);
|
|
1890
2224
|
const proxyCollector = new ProxyTelemetryCollector();
|
|
2225
|
+
const resolverCollector = new ResolverTelemetryCollector({ redact });
|
|
1891
2226
|
const telemetry = new RequestTelemetry(trace);
|
|
1892
2227
|
telemetry.register(proxyCollector);
|
|
2228
|
+
telemetry.register(resolverCollector);
|
|
1893
2229
|
let rootRunner: <T>(fn: () => Promise<T>) => Promise<T> = (fn) => fn();
|
|
1894
2230
|
let resolveRoot!: (outcome: RequestTerminalOutcome) => void;
|
|
1895
2231
|
const rootTerminal = new Promise<RequestTerminalOutcome>((resolve) => {
|
|
@@ -1913,12 +2249,14 @@ function createRequestScope(input: {
|
|
|
1913
2249
|
if (outcome.kind === "cancelled") return clientCancelledError();
|
|
1914
2250
|
return undefined;
|
|
1915
2251
|
};
|
|
1916
|
-
const root =
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2252
|
+
const root = withDiagnosticEnv(observeEnv, () =>
|
|
2253
|
+
trace.span(`request:${input.kind}:${input.route}`, async () => {
|
|
2254
|
+
rootRunner = AsyncLocalStorage.snapshot();
|
|
2255
|
+
const outcome = await rootTerminal;
|
|
2256
|
+
const error = terminalError(outcome);
|
|
2257
|
+
if (error !== undefined) throw error;
|
|
2258
|
+
}),
|
|
2259
|
+
);
|
|
1922
2260
|
void root.catch(() => undefined);
|
|
1923
2261
|
|
|
1924
2262
|
const settleRoot = (): void => {
|
|
@@ -1953,7 +2291,7 @@ function createRequestScope(input: {
|
|
|
1953
2291
|
traceAttributes.route = details.route;
|
|
1954
2292
|
const traceId = requestTraceId(details.headers, details.requestId);
|
|
1955
2293
|
const sanitizedAttributes = Object.fromEntries(
|
|
1956
|
-
Object.entries(sanitizeTraceAttributes(traceAttributes)).map(([key, value]) => [
|
|
2294
|
+
Object.entries(sanitizeTraceAttributes(traceAttributes, redact)).map(([key, value]) => [
|
|
1957
2295
|
key,
|
|
1958
2296
|
String(value),
|
|
1959
2297
|
]),
|
|
@@ -1965,8 +2303,16 @@ function createRequestScope(input: {
|
|
|
1965
2303
|
};
|
|
1966
2304
|
|
|
1967
2305
|
const scope: RequestScope = {
|
|
2306
|
+
redact,
|
|
1968
2307
|
trace,
|
|
1969
2308
|
telemetry,
|
|
2309
|
+
resolverTelemetry: resolverCollector,
|
|
2310
|
+
seedCredentials(rawBody, kind): void {
|
|
2311
|
+
const harvested = rawRequestCredentials(rawBody, kind);
|
|
2312
|
+
sensitiveRegistry.add(harvested.values);
|
|
2313
|
+
// An incomplete inventory cannot certify free text: emit less.
|
|
2314
|
+
if (harvested.exhausted) sensitiveRegistry.suppress();
|
|
2315
|
+
},
|
|
1970
2316
|
enrich(enrichment): void {
|
|
1971
2317
|
if (terminalOutcome) return;
|
|
1972
2318
|
if (enrichment.route !== undefined) details.route = enrichment.route;
|
|
@@ -2016,6 +2362,7 @@ function createRequestScope(input: {
|
|
|
2016
2362
|
},
|
|
2017
2363
|
terminalize(outcome): RequestScopeFinishResult {
|
|
2018
2364
|
if (finishedResult) return finishedResult;
|
|
2365
|
+
observeEnv.finished = true;
|
|
2019
2366
|
terminalOutcome = outcome;
|
|
2020
2367
|
const error = terminalError(outcome);
|
|
2021
2368
|
finishedResult = {};
|
|
@@ -2023,8 +2370,7 @@ function createRequestScope(input: {
|
|
|
2023
2370
|
const declaredErrorCode =
|
|
2024
2371
|
error === undefined ? undefined : input.declaredErrorCode?.(error);
|
|
2025
2372
|
const status =
|
|
2026
|
-
outcome.status ??
|
|
2027
|
-
(error === undefined ? 200 : toStatusCode(error, declaredErrorCode));
|
|
2373
|
+
outcome.status ?? (error === undefined ? 200 : toStatusCode(error, declaredErrorCode));
|
|
2028
2374
|
finishedResult = headerSnapshot(error);
|
|
2029
2375
|
const cost = finishRequestCost(requestCost);
|
|
2030
2376
|
try {
|
|
@@ -2039,6 +2385,7 @@ function createRequestScope(input: {
|
|
|
2039
2385
|
cost,
|
|
2040
2386
|
telemetry,
|
|
2041
2387
|
details.correlation,
|
|
2388
|
+
redact,
|
|
2042
2389
|
);
|
|
2043
2390
|
} else {
|
|
2044
2391
|
logProviderError(
|
|
@@ -2054,6 +2401,7 @@ function createRequestScope(input: {
|
|
|
2054
2401
|
telemetry,
|
|
2055
2402
|
finishedResult.errorObservability as ErrorObservabilityDetails,
|
|
2056
2403
|
details.correlation,
|
|
2404
|
+
redact,
|
|
2057
2405
|
);
|
|
2058
2406
|
}
|
|
2059
2407
|
} catch {
|
|
@@ -2089,35 +2437,73 @@ function createRequestScope(input: {
|
|
|
2089
2437
|
return scope;
|
|
2090
2438
|
}
|
|
2091
2439
|
|
|
2092
|
-
|
|
2440
|
+
// A raw `Response` result skips `toErrorResponse`. Its payload shape stays
|
|
2441
|
+
// provider-owned; known credentials in error payloads still need redaction.
|
|
2442
|
+
// The observability header is SDK-owned: derive it
|
|
2443
|
+
// from the status. Only timeouts, rate limits, and 5xx are retryable;
|
|
2444
|
+
// `isRetryableCategory` would otherwise mark every other 4xx (`upstream_http`)
|
|
2445
|
+
// as retryable.
|
|
2446
|
+
function rawResponseErrorObservability(status: number): ErrorObservabilityDetails {
|
|
2447
|
+
return {
|
|
2448
|
+
category: categoryForStatus(status),
|
|
2449
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
2450
|
+
retryable: status === 408 || status === 429 || status >= 500,
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
// `rawStatusFallback` is decided by the caller from the outcome, not from a
|
|
2455
|
+
// missing snapshot: a failed outcome whose thrown value is `undefined` also has
|
|
2456
|
+
// no `errorObservability`, and its 500 carries the SDK envelope, not a raw body.
|
|
2457
|
+
async function responseWithRequestScopeHeaders(
|
|
2093
2458
|
response: Response,
|
|
2094
2459
|
finished: RequestScopeFinishResult,
|
|
2095
|
-
|
|
2460
|
+
rawStatusFallback: boolean,
|
|
2461
|
+
redact: DiagnosticRedactor,
|
|
2462
|
+
): Promise<Response> {
|
|
2096
2463
|
const headers = new Headers(response.headers);
|
|
2097
2464
|
headers.delete(PROVIDER_TELEMETRY_HEADER);
|
|
2098
2465
|
if (finished.providerTelemetryHeader) {
|
|
2099
2466
|
headers.set(PROVIDER_TELEMETRY_HEADER, finished.providerTelemetryHeader);
|
|
2100
2467
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2468
|
+
headers.delete(ERROR_OBSERVABILITY_HEADER);
|
|
2469
|
+
const errorObservability =
|
|
2470
|
+
finished.errorObservability ??
|
|
2471
|
+
(rawStatusFallback && response.status >= 400
|
|
2472
|
+
? rawResponseErrorObservability(response.status)
|
|
2473
|
+
: undefined);
|
|
2474
|
+
if (errorObservability) {
|
|
2475
|
+
headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservability));
|
|
2476
|
+
}
|
|
2477
|
+
// Raw Response bodies are provider-owned. Preserve their stream and representation.
|
|
2478
|
+
const body = response.body;
|
|
2479
|
+
const statusText =
|
|
2480
|
+
rawStatusFallback && response.status >= 400
|
|
2481
|
+
? redactDiagnosticText(response.statusText, redact)
|
|
2482
|
+
: response.statusText;
|
|
2483
|
+
return new Response(body, {
|
|
2105
2484
|
headers,
|
|
2106
2485
|
status: response.status,
|
|
2107
|
-
|
|
2486
|
+
// When free text cannot be certified, use the SDK's standard HTTP reason
|
|
2487
|
+
// phrase. It preserves the status contract without trusting provider text.
|
|
2488
|
+
statusText:
|
|
2489
|
+
statusText === REDACTION_FAILED && isDiagnosticRedactionSuppressed(redact)
|
|
2490
|
+
? (STATUS_CODES[response.status] ?? "")
|
|
2491
|
+
: statusText,
|
|
2108
2492
|
});
|
|
2109
2493
|
}
|
|
2110
2494
|
|
|
2111
|
-
function finalizeRequestResponse(
|
|
2495
|
+
async function finalizeRequestResponse(
|
|
2112
2496
|
scope: RequestScope,
|
|
2113
2497
|
response: Response,
|
|
2114
2498
|
outcome: RequestTerminalOutcome,
|
|
2115
|
-
): Response {
|
|
2499
|
+
): Promise<Response> {
|
|
2116
2500
|
try {
|
|
2117
2501
|
const error = outcome.kind === "failed" ? outcome.error : undefined;
|
|
2118
|
-
const finalResponse = responseWithRequestScopeHeaders(
|
|
2502
|
+
const finalResponse = await responseWithRequestScopeHeaders(
|
|
2119
2503
|
response,
|
|
2120
2504
|
scope.snapshotHeaders(error),
|
|
2505
|
+
outcome.kind === "completed",
|
|
2506
|
+
scope.redact,
|
|
2121
2507
|
);
|
|
2122
2508
|
scope.terminalize(outcome);
|
|
2123
2509
|
return finalResponse;
|
|
@@ -2336,11 +2722,7 @@ function toSseResponse(
|
|
|
2336
2722
|
const validated = await validateSseEvent(operation, next.value);
|
|
2337
2723
|
const encodedEvent = encodeSseEvent(validated);
|
|
2338
2724
|
const bytes = encoder.encode(encodedEvent);
|
|
2339
|
-
assertStreamPayloadWithinLimit(
|
|
2340
|
-
bytes.byteLength,
|
|
2341
|
-
transport?.maxEventBytes,
|
|
2342
|
-
"event",
|
|
2343
|
-
);
|
|
2725
|
+
assertStreamPayloadWithinLimit(bytes.byteLength, transport?.maxEventBytes, "event");
|
|
2344
2726
|
return bytes;
|
|
2345
2727
|
});
|
|
2346
2728
|
controller.enqueue(encodedBytes);
|
|
@@ -2349,9 +2731,13 @@ function toSseResponse(
|
|
|
2349
2731
|
controller.enqueue(
|
|
2350
2732
|
encoder.encode(
|
|
2351
2733
|
encodeSseEvent(
|
|
2352
|
-
streamError(
|
|
2353
|
-
|
|
2354
|
-
|
|
2734
|
+
streamError(
|
|
2735
|
+
"stream_error",
|
|
2736
|
+
sanitizeDiagnosticText(redactDiagnosticText(message, lifecycle.redact)),
|
|
2737
|
+
{
|
|
2738
|
+
...(requestId ? { requestId } : {}),
|
|
2739
|
+
},
|
|
2740
|
+
),
|
|
2355
2741
|
),
|
|
2356
2742
|
),
|
|
2357
2743
|
);
|
|
@@ -2484,6 +2870,7 @@ function getHttpStreamTransport(
|
|
|
2484
2870
|
function toAuthFlowResponse(
|
|
2485
2871
|
result: unknown,
|
|
2486
2872
|
contextPatch: Record<string, unknown | null> | undefined,
|
|
2873
|
+
engine: AuthFlowSuccessResponse["engine"],
|
|
2487
2874
|
): Response | AuthFlowSuccessResponse {
|
|
2488
2875
|
if (result instanceof Response) {
|
|
2489
2876
|
return result;
|
|
@@ -2496,6 +2883,7 @@ function toAuthFlowResponse(
|
|
|
2496
2883
|
return {
|
|
2497
2884
|
data: result,
|
|
2498
2885
|
...(contextPatch ? { contextPatch } : {}),
|
|
2886
|
+
...(engine ? { engine } : {}),
|
|
2499
2887
|
};
|
|
2500
2888
|
}
|
|
2501
2889
|
|
|
@@ -2592,6 +2980,7 @@ async function handleOperation(
|
|
|
2592
2980
|
request.requestId,
|
|
2593
2981
|
"stealth",
|
|
2594
2982
|
error,
|
|
2983
|
+
scope.redact,
|
|
2595
2984
|
);
|
|
2596
2985
|
}
|
|
2597
2986
|
}
|
|
@@ -2607,11 +2996,13 @@ async function handleOperation(
|
|
|
2607
2996
|
request.requestId,
|
|
2608
2997
|
"browser",
|
|
2609
2998
|
error,
|
|
2999
|
+
scope.redact,
|
|
2610
3000
|
);
|
|
2611
3001
|
}
|
|
2612
3002
|
}
|
|
2613
3003
|
};
|
|
2614
3004
|
const streamLifecycle: RequestStreamLifecycle = {
|
|
3005
|
+
redact: scope.redact,
|
|
2615
3006
|
runStep<T>(fn: () => Promise<T>): Promise<T> {
|
|
2616
3007
|
return scope.runStreamStep(fn);
|
|
2617
3008
|
},
|
|
@@ -2672,10 +3063,12 @@ async function handleAuthFlow(
|
|
|
2672
3063
|
// depend on declared secrets (client ids/secrets), so fail structured before
|
|
2673
3064
|
// any flow code runs instead of at whatever point the ceremony first reads
|
|
2674
3065
|
// the env. `abort` stays exempt: a user must always be able to cancel a
|
|
2675
|
-
// stranded flow even when provisioning is broken
|
|
2676
|
-
|
|
3066
|
+
// stranded flow even when provisioning is broken (createAuthFlowContext
|
|
3067
|
+
// applies the same exemption to the engine ceremony lease).
|
|
3068
|
+
const { context, getPatch, getEngineState } = createAuthFlowContext(
|
|
2677
3069
|
provider,
|
|
2678
3070
|
request,
|
|
3071
|
+
route,
|
|
2679
3072
|
options,
|
|
2680
3073
|
state,
|
|
2681
3074
|
scope,
|
|
@@ -2715,10 +3108,10 @@ async function handleAuthFlow(
|
|
|
2715
3108
|
isAuthTurn(result)
|
|
2716
3109
|
? materializeAuthFlowTurn(provider, request, result)
|
|
2717
3110
|
: result;
|
|
2718
|
-
return toAuthFlowResponse(materializedResult, getPatch());
|
|
3111
|
+
return toAuthFlowResponse(materializedResult, getPatch(), getEngineState());
|
|
2719
3112
|
} catch (error) {
|
|
2720
3113
|
if (error instanceof AuthAbortError) {
|
|
2721
|
-
return toAuthFlowResponse(error.turn, getPatch());
|
|
3114
|
+
return toAuthFlowResponse(error.turn, getPatch(), getEngineState());
|
|
2722
3115
|
}
|
|
2723
3116
|
throw error;
|
|
2724
3117
|
} finally {
|
|
@@ -2733,6 +3126,7 @@ async function handleAuthFlow(
|
|
|
2733
3126
|
request.requestId,
|
|
2734
3127
|
"stealth",
|
|
2735
3128
|
error,
|
|
3129
|
+
scope.redact,
|
|
2736
3130
|
);
|
|
2737
3131
|
}
|
|
2738
3132
|
}
|
|
@@ -2854,12 +3248,15 @@ function operationRequestFromForwardingEnvelope(
|
|
|
2854
3248
|
};
|
|
2855
3249
|
}
|
|
2856
3250
|
|
|
2857
|
-
function parseStatefulForwardingEnvelope(
|
|
3251
|
+
function parseStatefulForwardingEnvelope(
|
|
3252
|
+
rawBody: unknown,
|
|
3253
|
+
redact: DiagnosticRedactor,
|
|
3254
|
+
): ProviderServerStatefulForwardEnvelope {
|
|
2858
3255
|
const parsed = ProviderServerStatefulForwardEnvelopeSchema.safeParse(rawBody);
|
|
2859
3256
|
if (parsed.success) return parsed.data;
|
|
2860
3257
|
throw new ProviderError("Stateful forwarding envelope is invalid.", {
|
|
2861
3258
|
code: "STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
2862
|
-
details: zodDetails(parsed.error),
|
|
3259
|
+
details: zodDetails(parsed.error, redact),
|
|
2863
3260
|
});
|
|
2864
3261
|
}
|
|
2865
3262
|
|
|
@@ -2867,7 +3264,9 @@ function parseStatefulForwardingEnvelope(rawBody: unknown): ProviderServerStatef
|
|
|
2867
3264
|
* Primary, cross-runtime app factory. Declared capability ESM is preloaded
|
|
2868
3265
|
* asynchronously, so this path works on Bun and every supported Node release.
|
|
2869
3266
|
*/
|
|
2870
|
-
export async function createServerAppAsync<
|
|
3267
|
+
export async function createServerAppAsync<
|
|
3268
|
+
TContext extends Partial<ProviderContext> = ProviderContext,
|
|
3269
|
+
>(
|
|
2871
3270
|
provider: ProviderDefinition<TContext>,
|
|
2872
3271
|
options: ProviderServerOptions<TContext> = {},
|
|
2873
3272
|
): Promise<Hono> {
|
|
@@ -2914,6 +3313,12 @@ function createServerAppWithCapabilityModules(
|
|
|
2914
3313
|
engine: serverOptions.engine ?? createInProcessProviderEngine(),
|
|
2915
3314
|
};
|
|
2916
3315
|
const app = new Hono();
|
|
3316
|
+
// Compile the startup inventory once, composed with the shared outside-context fallback.
|
|
3317
|
+
const staticSensitiveValues = compileProcessDiagnosticSensitiveValues(
|
|
3318
|
+
collectStaticDiagnosticSensitiveValues(provider, {
|
|
3319
|
+
statefulForwardingSecret: options.statefulForwarding?.secret,
|
|
3320
|
+
}),
|
|
3321
|
+
);
|
|
2917
3322
|
const logger = options.logger ?? defaultProviderServerLogger;
|
|
2918
3323
|
const operationErrorCodes = buildOperationErrorCodeLookup(provider);
|
|
2919
3324
|
const statefulForwardingReplayCache = new StatefulForwardingReplayCache(
|
|
@@ -2969,6 +3374,7 @@ function createServerAppWithCapabilityModules(
|
|
|
2969
3374
|
const operation = "stateful-internal";
|
|
2970
3375
|
const requestScope = createRequestScope({
|
|
2971
3376
|
provider,
|
|
3377
|
+
staticSensitiveValues,
|
|
2972
3378
|
kind: "operation",
|
|
2973
3379
|
route: operation,
|
|
2974
3380
|
headers: Object.fromEntries(c.req.raw.headers.entries()),
|
|
@@ -2998,7 +3404,8 @@ function createServerAppWithCapabilityModules(
|
|
|
2998
3404
|
code: "STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
2999
3405
|
});
|
|
3000
3406
|
}
|
|
3001
|
-
|
|
3407
|
+
requestScope.seedCredentials(rawBody, "stateful");
|
|
3408
|
+
const envelope = parseStatefulForwardingEnvelope(rawBody, requestScope.redact);
|
|
3002
3409
|
if (envelope.providerId !== provider.id) {
|
|
3003
3410
|
throw new ProviderError(
|
|
3004
3411
|
"Stateful forwarding envelope providerId does not match the served provider.",
|
|
@@ -3134,7 +3541,10 @@ function createServerAppWithCapabilityModules(
|
|
|
3134
3541
|
...(requestId ? { requestId } : {}),
|
|
3135
3542
|
});
|
|
3136
3543
|
const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
|
|
3137
|
-
const response = c.json(
|
|
3544
|
+
const response = c.json(
|
|
3545
|
+
toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
|
|
3546
|
+
status,
|
|
3547
|
+
);
|
|
3138
3548
|
return finalizeRequestResponse(requestScope, response, {
|
|
3139
3549
|
kind: "failed",
|
|
3140
3550
|
status,
|
|
@@ -3148,6 +3558,7 @@ function createServerAppWithCapabilityModules(
|
|
|
3148
3558
|
const operation = c.req.param("operation");
|
|
3149
3559
|
const requestScope = createRequestScope({
|
|
3150
3560
|
provider,
|
|
3561
|
+
staticSensitiveValues,
|
|
3151
3562
|
kind: "operation",
|
|
3152
3563
|
route: operation,
|
|
3153
3564
|
operationId: operation,
|
|
@@ -3161,6 +3572,7 @@ function createServerAppWithCapabilityModules(
|
|
|
3161
3572
|
.clone()
|
|
3162
3573
|
.json()
|
|
3163
3574
|
.catch(() => undefined);
|
|
3575
|
+
requestScope.seedCredentials(rawBody, "operation");
|
|
3164
3576
|
const body = OperationRequestSchema.parse(rawBody);
|
|
3165
3577
|
const requestHeaders = Object.fromEntries(c.req.raw.headers.entries());
|
|
3166
3578
|
body.headers = { ...requestHeaders, ...body.headers };
|
|
@@ -3185,7 +3597,12 @@ function createServerAppWithCapabilityModules(
|
|
|
3185
3597
|
);
|
|
3186
3598
|
const response = handled instanceof Response ? handled : c.json(handled);
|
|
3187
3599
|
return streaming
|
|
3188
|
-
? responseWithRequestScopeHeaders(
|
|
3600
|
+
? responseWithRequestScopeHeaders(
|
|
3601
|
+
response,
|
|
3602
|
+
requestScope.snapshotHeaders(),
|
|
3603
|
+
true,
|
|
3604
|
+
requestScope.redact,
|
|
3605
|
+
)
|
|
3189
3606
|
: response;
|
|
3190
3607
|
};
|
|
3191
3608
|
const response = streaming
|
|
@@ -3202,7 +3619,10 @@ function createServerAppWithCapabilityModules(
|
|
|
3202
3619
|
const requestId = extractRequestId(rawBody);
|
|
3203
3620
|
requestScope.enrich({ ...(requestId ? { requestId } : {}) });
|
|
3204
3621
|
const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
|
|
3205
|
-
const response = c.json(
|
|
3622
|
+
const response = c.json(
|
|
3623
|
+
toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
|
|
3624
|
+
status,
|
|
3625
|
+
);
|
|
3206
3626
|
return finalizeRequestResponse(requestScope, response, {
|
|
3207
3627
|
kind: "failed",
|
|
3208
3628
|
status,
|
|
@@ -3228,6 +3648,7 @@ function createServerAppWithCapabilityModules(
|
|
|
3228
3648
|
let rawBody: unknown;
|
|
3229
3649
|
const requestScope = createRequestScope({
|
|
3230
3650
|
provider,
|
|
3651
|
+
staticSensitiveValues,
|
|
3231
3652
|
kind: "auth",
|
|
3232
3653
|
route: logRoute,
|
|
3233
3654
|
headers: Object.fromEntries(c.req.raw.headers.entries()),
|
|
@@ -3239,6 +3660,7 @@ function createServerAppWithCapabilityModules(
|
|
|
3239
3660
|
.clone()
|
|
3240
3661
|
.json()
|
|
3241
3662
|
.catch(() => undefined);
|
|
3663
|
+
requestScope.seedCredentials(rawBody, "auth");
|
|
3242
3664
|
const body = withAuthRequestHeaders(
|
|
3243
3665
|
AuthFlowRequestSchema.parse(rawBody),
|
|
3244
3666
|
c.req.raw.headers,
|
|
@@ -3278,7 +3700,10 @@ function createServerAppWithCapabilityModules(
|
|
|
3278
3700
|
const requestId = extractRequestId(rawBody);
|
|
3279
3701
|
requestScope.enrich({ ...(requestId ? { requestId } : {}) });
|
|
3280
3702
|
const observabilityDetails = errorObservabilityDetails(error);
|
|
3281
|
-
const response = c.json(
|
|
3703
|
+
const response = c.json(
|
|
3704
|
+
toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
|
|
3705
|
+
status,
|
|
3706
|
+
);
|
|
3282
3707
|
return finalizeRequestResponse(requestScope, response, {
|
|
3283
3708
|
kind: "failed",
|
|
3284
3709
|
status,
|