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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/AUTHORING.md +9 -0
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +22 -5
  4. package/SUBMISSION.md +2 -1
  5. package/bin/apifuse-pack-types.ts +49 -0
  6. package/bin/apifuse-record.ts +6 -0
  7. package/dist/cli/templates/provider/README.md.tpl +19 -5
  8. package/dist/config/loader.d.ts +7 -0
  9. package/dist/config/loader.js +30 -6
  10. package/dist/declaration-validation.d.ts +2 -1
  11. package/dist/declaration-validation.js +39 -0
  12. package/dist/define.d.ts +5 -4
  13. package/dist/define.js +25 -1
  14. package/dist/engine.d.ts +16 -0
  15. package/dist/engine.js +58 -1
  16. package/dist/error-resolution.js +13 -0
  17. package/dist/index.d.ts +6 -4
  18. package/dist/index.js +4 -2
  19. package/dist/lint.js +3 -3
  20. package/dist/provider.d.ts +3 -3
  21. package/dist/provider.js +2 -2
  22. package/dist/runtime/bounded-expiring-map.d.ts +21 -0
  23. package/dist/runtime/bounded-expiring-map.js +64 -0
  24. package/dist/runtime/cache.d.ts +2 -0
  25. package/dist/runtime/cache.js +8 -1
  26. package/dist/runtime/egress-lease.d.ts +63 -0
  27. package/dist/runtime/egress-lease.js +233 -0
  28. package/dist/runtime/http.js +1 -0
  29. package/dist/runtime/proxy-retry-policy.d.ts +4 -1
  30. package/dist/runtime/proxy-retry-policy.js +11 -2
  31. package/dist/runtime/request-telemetry.d.ts +7 -9
  32. package/dist/runtime/resolver-config.d.ts +7 -0
  33. package/dist/runtime/resolver-config.js +8 -0
  34. package/dist/runtime/resolver-public.d.ts +3 -1
  35. package/dist/runtime/resolver-public.js +1 -1
  36. package/dist/runtime/resolver-shared.d.ts +9 -0
  37. package/dist/runtime/resolver-shared.js +57 -0
  38. package/dist/runtime/resolver-telemetry.d.ts +134 -0
  39. package/dist/runtime/resolver-telemetry.js +204 -0
  40. package/dist/runtime/resolver-usage.d.ts +24 -0
  41. package/dist/runtime/resolver-usage.js +60 -0
  42. package/dist/runtime/resolver-vendors/bindings.d.ts +5 -0
  43. package/dist/runtime/resolver-vendors/bindings.js +5 -0
  44. package/dist/runtime/resolver-vendors/capsolver.d.ts +2 -2
  45. package/dist/runtime/resolver-vendors/capsolver.js +21 -9
  46. package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
  47. package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
  48. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -2
  49. package/dist/runtime/resolver-vendors/twocaptcha.js +26 -10
  50. package/dist/runtime/resolver-vendors/types.d.ts +35 -5
  51. package/dist/runtime/resolver-vendors/types.js +20 -10
  52. package/dist/runtime/resolver.d.ts +5 -1
  53. package/dist/runtime/resolver.js +397 -109
  54. package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
  55. package/dist/runtime/stealth-akamai-sbsd.js +142 -0
  56. package/dist/runtime/stealth.js +547 -53
  57. package/dist/schema.d.ts +9 -0
  58. package/dist/schema.js +16 -0
  59. package/dist/server/index.d.ts +2 -0
  60. package/dist/server/index.js +1 -0
  61. package/dist/server/self-test.d.ts +4 -0
  62. package/dist/server/self-test.js +10 -1
  63. package/dist/server/serve-implementation.d.ts +7 -1
  64. package/dist/server/serve-implementation.js +181 -53
  65. package/dist/server/types.d.ts +6 -0
  66. package/dist/server/types.js +14 -0
  67. package/dist/stealth/profiles.d.ts +7 -1
  68. package/dist/stealth/profiles.js +9 -0
  69. package/dist/testing/run.js +6 -0
  70. package/dist/types.d.ts +115 -12
  71. package/package.json +1 -1
  72. package/src/cli/templates/provider/README.md.tpl +19 -5
  73. package/src/config/loader.ts +34 -6
  74. package/src/declaration-validation.ts +47 -1
  75. package/src/define.ts +46 -3
  76. package/src/engine.ts +76 -1
  77. package/src/error-resolution.ts +13 -0
  78. package/src/index.ts +32 -1
  79. package/src/lint.ts +3 -3
  80. package/src/provider.ts +9 -0
  81. package/src/runtime/bounded-expiring-map.ts +67 -0
  82. package/src/runtime/cache.ts +10 -1
  83. package/src/runtime/egress-lease.ts +336 -0
  84. package/src/runtime/http.ts +1 -0
  85. package/src/runtime/proxy-retry-policy.ts +12 -1
  86. package/src/runtime/request-telemetry.ts +5 -7
  87. package/src/runtime/resolver-config.ts +12 -0
  88. package/src/runtime/resolver-public.ts +21 -0
  89. package/src/runtime/resolver-shared.ts +75 -0
  90. package/src/runtime/resolver-telemetry.ts +362 -0
  91. package/src/runtime/resolver-usage.ts +96 -0
  92. package/src/runtime/resolver-vendors/bindings.ts +5 -0
  93. package/src/runtime/resolver-vendors/capsolver.ts +22 -3
  94. package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
  95. package/src/runtime/resolver-vendors/twocaptcha.ts +26 -12
  96. package/src/runtime/resolver-vendors/types.ts +60 -12
  97. package/src/runtime/resolver.ts +465 -124
  98. package/src/runtime/stealth-akamai-sbsd.ts +207 -0
  99. package/src/runtime/stealth.ts +727 -95
  100. package/src/schema.ts +17 -0
  101. package/src/server/index.ts +21 -0
  102. package/src/server/self-test.ts +13 -0
  103. package/src/server/serve-implementation.ts +268 -48
  104. package/src/server/types.ts +14 -0
  105. package/src/stealth/profiles.ts +10 -0
  106. package/src/testing/run.ts +6 -0
  107. package/src/types.ts +130 -22
package/dist/schema.d.ts CHANGED
@@ -13,6 +13,8 @@ export declare function safeParseSchemaSync<TSchema extends SchemaLike>(schema:
13
13
  export declare const APIFUSE_SENSITIVE_META_KEY = "x-apifuse-sensitive";
14
14
  export declare const APIFUSE_SENSITIVE_KIND_META_KEY = "x-apifuse-sensitive-kind";
15
15
  export declare const APIFUSE_DESCRIPTION_KEY_META_KEY = "x-apifuse-description-key";
16
+ export declare const APIFUSE_CONTENT_TRUST_META_KEY = "x-apifuse-content-trust";
17
+ export declare const APIFUSE_CONTENT_PROVENANCE_META_KEY = "x-apifuse-content-provenance";
16
18
  export declare const APIFUSE_REDACTION_MARKER = "<redacted>";
17
19
  export type SensitivePathSegment = string | "*";
18
20
  export type SensitivePath = readonly SensitivePathSegment[];
@@ -32,6 +34,13 @@ export interface SensitiveFieldOptions {
32
34
  description?: string;
33
35
  }
34
36
  export declare function describeKey<TSchema extends ZodType>(schema: TSchema, key: ProviderLocaleKey | string): TSchema;
37
+ /**
38
+ * Mark an output field as externally authored text (listing bodies, reviews,
39
+ * posts) that the provider relays verbatim. Sets both content-trust JSON Schema
40
+ * meta keys on the field node so the platform can enforce the boundary when it
41
+ * assembles tool output; validation and the parsed value are unchanged.
42
+ */
43
+ export declare function untrustedContent<TSchema extends ZodType>(schema: TSchema): TSchema;
35
44
  declare module "zod" {
36
45
  interface ZodType {
37
46
  describeKey(key: ProviderLocaleKey | string): this;
package/dist/schema.js CHANGED
@@ -41,6 +41,8 @@ export function safeParseSchemaSync(schema, value, fieldPath) {
41
41
  export const APIFUSE_SENSITIVE_META_KEY = "x-apifuse-sensitive";
42
42
  export const APIFUSE_SENSITIVE_KIND_META_KEY = "x-apifuse-sensitive-kind";
43
43
  export const APIFUSE_DESCRIPTION_KEY_META_KEY = "x-apifuse-description-key";
44
+ export const APIFUSE_CONTENT_TRUST_META_KEY = "x-apifuse-content-trust";
45
+ export const APIFUSE_CONTENT_PROVENANCE_META_KEY = "x-apifuse-content-provenance";
44
46
  export const APIFUSE_REDACTION_MARKER = "<redacted>";
45
47
  export function describeKey(schema, key) {
46
48
  const descriptionKey = providerLocaleKey(key);
@@ -50,6 +52,20 @@ export function describeKey(schema, key) {
50
52
  [APIFUSE_DESCRIPTION_KEY_META_KEY]: descriptionKey,
51
53
  });
52
54
  }
55
+ /**
56
+ * Mark an output field as externally authored text (listing bodies, reviews,
57
+ * posts) that the provider relays verbatim. Sets both content-trust JSON Schema
58
+ * meta keys on the field node so the platform can enforce the boundary when it
59
+ * assembles tool output; validation and the parsed value are unchanged.
60
+ */
61
+ export function untrustedContent(schema) {
62
+ const metadata = schema.meta() ?? {};
63
+ return schema.meta({
64
+ ...metadata,
65
+ [APIFUSE_CONTENT_TRUST_META_KEY]: "untrusted",
66
+ [APIFUSE_CONTENT_PROVENANCE_META_KEY]: "external",
67
+ });
68
+ }
53
69
  const describeKeyMethod = function (key) {
54
70
  return describeKey(this, key);
55
71
  };
@@ -1,6 +1,8 @@
1
1
  export type { ProxyCacheStatus, ProxyProtocol, ProxyUserAgentSource, ProxyVendorName, SmartproxyAllocatorBodyClass, } from "../config/loader.js";
2
2
  export type { ProxyTelemetryHeaderPayload, ProxyHash, ProxyTelemetryLogPayload, ProxyTelemetryResolvedPayload, ProxyTelemetryUnresolvedPayload, } from "../runtime/proxy-telemetry.js";
3
3
  export { RequestTelemetry, closedEnum, type ClosedEnum, type GatewayIngestible, type RequestTelemetryLogPayload, type SpanIndex, type TelemetryContributor, type TelemetryKey, type TenantNeutral, } from "../runtime/request-telemetry.js";
4
+ export { ResolverTelemetryCollector, type ResolverAttemptSample, type ResolverCacheReadTelemetryEvent, type ResolverCacheWriteTelemetryEvent, type ResolverFailoverTelemetryEvent, type ResolverIdentityTelemetryEvent, type ResolverOutcomeTelemetryEvent, type ResolverTelemetryAttemptOutcome, type ResolverTelemetryCacheStatus, type ResolverTelemetryCacheWriteReason, type ResolverTelemetryErrorClass, type ResolverTelemetryHeaderPayload, type ResolverTelemetryIdentitySource, type ResolverTelemetryLogPayload, type ResolverTelemetryOutcome, type ResolverTelemetryPhase, type ResolverTelemetrySink, type ResolverVendorAttemptTelemetryEvent, } from "../runtime/resolver-telemetry.js";
5
+ export type { ResolverVendorUnavailableReason } from "../runtime/resolver-vendors/types.js";
4
6
  export type { Span, TraceContext } from "../runtime/trace.js";
5
7
  export { createServerApp, createServerAppAsync, ERROR_OBSERVABILITY_HEADER, type ErrorObservabilityDetails, type ProviderServerCloseOptions, type ProviderServerHandle, type ProviderErrorCauseFrame, type ProviderServerLogEvent, type ProviderServerLogger, type ProviderServerOperationExecutor, type ProviderServerOperationExecutorInput, type ProviderServerOptions, type ProviderServerStatefulForwardEnvelope, type ServeOptions, serve, } from "./serve.js";
6
8
  export type { ProviderErrorObservability } from "../errors.js";
@@ -1,4 +1,5 @@
1
1
  export { RequestTelemetry, closedEnum, } from "../runtime/request-telemetry.js";
2
+ export { ResolverTelemetryCollector, } from "../runtime/resolver-telemetry.js";
2
3
  export { createServerApp, createServerAppAsync, ERROR_OBSERVABILITY_HEADER, serve, } from "./serve.js";
3
4
  export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, SelfTestRequestSchema, } from "./self-test.js";
4
5
  export { resolveHealthCheckInputDateTokens, } from "./self-test-input-tokens.js";
@@ -88,6 +88,10 @@ export type SelfTestAuthFlowInvoke = (args: {
88
88
  externalRef?: string;
89
89
  input?: Record<string, unknown>;
90
90
  context?: Record<string, unknown>;
91
+ /** Opaque engine state from the previous turn (`engine` in the auth response), sent verbatim. */
92
+ engine?: {
93
+ egressLease: string;
94
+ };
91
95
  signal?: AbortSignal;
92
96
  }) => Promise<{
93
97
  status: number;
@@ -187,17 +187,21 @@ function logSelfTestCancellation(provider, options, requestId, selected, signal,
187
187
  }
188
188
  /** Binds the self-test auth-flow driver to a tenant app's /auth pipeline in-process. */
189
189
  export function createSelfTestAuthFlowInvoke(app) {
190
- return async ({ route, requestId, flowId, connectionId, externalRef, input, context, signal, }) => {
190
+ return async ({ route, requestId, flowId, connectionId, externalRef, input, context, engine, signal, }) => {
191
191
  const response = await app.request(`/auth/${route}`, {
192
192
  method: "POST",
193
193
  headers: { "content-type": "application/json" },
194
194
  body: JSON.stringify({
195
195
  requestId,
196
196
  flowId,
197
+ // Engine ceremony leases are scoped to a tenant; the self-test has no
198
+ // customer tenant, so it presents a fixed one that never collides.
199
+ tenantId: "self-test",
197
200
  ...(connectionId ? { connectionId } : {}),
198
201
  ...(externalRef ? { externalRef } : {}),
199
202
  ...(input ? { input } : {}),
200
203
  ...(context ? { context } : {}),
204
+ ...(engine ? { engine } : {}),
201
205
  }),
202
206
  ...(signal ? { signal } : {}),
203
207
  });
@@ -329,6 +333,7 @@ function parseAuthFlowResponse(result) {
329
333
  };
330
334
  }
331
335
  const contextPatch = objectProperty(result.body, "contextPatch");
336
+ const egressLease = objectProperty(objectProperty(result.body, "engine"), "egressLease");
332
337
  return {
333
338
  ok: true,
334
339
  turn: {
@@ -339,6 +344,7 @@ function parseAuthFlowResponse(result) {
339
344
  ...(contextPatch && typeof contextPatch === "object" && !Array.isArray(contextPatch)
340
345
  ? { contextPatch: contextPatch }
341
346
  : {}),
347
+ ...(typeof egressLease === "string" && egressLease ? { engine: { egressLease } } : {}),
342
348
  };
343
349
  }
344
350
  function applyAuthFlowContextPatch(base, patch) {
@@ -499,6 +505,9 @@ async function materializeFlowCredential(execution, inputs, options = {}) {
499
505
  ...(options.externalRef ? { externalRef: options.externalRef } : {}),
500
506
  input: submitInputs,
501
507
  ...(Object.keys(flowContext).length > 0 ? { context: flowContext } : {}),
508
+ // The ceremony egress lease is engine state, not flow context: carry it the
509
+ // way the gateway does so the continue turn stays on the start turn's egress.
510
+ ...(started.engine ? { engine: started.engine } : {}),
502
511
  ...(options.signal ? { signal: options.signal } : {}),
503
512
  }));
504
513
  if (!continued.ok) {
@@ -4,6 +4,7 @@ import { type ProviderEngine } from "../engine.js";
4
4
  import { type ProviderErrorObservability } from "../errors.js";
5
5
  import { type ProviderErrorCategory } from "../observability.js";
6
6
  import { type ProxyTelemetryLogPayload } from "../runtime/proxy-telemetry.js";
7
+ import { type ResolverTelemetryLogPayload } from "../runtime/resolver-telemetry.js";
7
8
  import type { OcrContext, ProviderContext, ProviderDefinition, ProviderFilesContext, ProviderRuntimeState, ResolverContext, SttContext } from "../types.js";
8
9
  import type { SelfTestCancellationLogEvent } from "./self-test.js";
9
10
  import { type AuthFlowRequest, type OperationRequest } from "./types.js";
@@ -85,6 +86,7 @@ type ProviderServerLogEventBase = ProviderRequestCost & {
85
86
  requestedProviderId?: string;
86
87
  status: number;
87
88
  proxy?: ProxyTelemetryLogPayload;
89
+ resolver?: ResolverTelemetryLogPayload;
88
90
  };
89
91
  export type ProviderServerLogEvent = (ProviderServerLogEventBase & {
90
92
  level: "info";
@@ -164,7 +166,11 @@ export type ProviderServerOptions<TContext extends Partial<ProviderContext> = Pr
164
166
  stt?: SttContext;
165
167
  /** Optional OCR override for tests or custom hosts; local/prod normally resolves from env. */
166
168
  ocr?: OcrContext;
167
- /** Optional resolver override for tests or custom hosts; local/prod normally resolves from env. */
169
+ /**
170
+ * Optional resolver override for tests or custom hosts; local/prod normally resolves from env.
171
+ * SDK chains are reconstructed with each request's telemetry sink. An opaque host
172
+ * resolver is reported as one custom invocation; its internal vendor work is not exposed.
173
+ */
168
174
  resolver?: ResolverContext;
169
175
  /** Optional runtime state override for tests or custom hosts. Production resolves Redis from env and fails closed when unavailable. */
170
176
  state?: ProviderRuntimeState;
@@ -10,7 +10,7 @@ import { validateFailClosedDeclaration } from "../declaration-validation.js";
10
10
  import { safeProviderErrorObservability } from "../error-observability.js";
11
11
  import { createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, isEngineOwnedEnvName, readEngineProxyCredentials, } from "../engine.js";
12
12
  import { SDK_OWNED_PROVIDER_ERROR_CODES, SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "../error-resolution.js";
13
- import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, } from "../errors.js";
13
+ import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, SDKError, } from "../errors.js";
14
14
  import { sanitizeDiagnosticText } from "../fixture-sanitization.js";
15
15
  import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog.js";
16
16
  import { categoryForStatus, sourceForCategory, isRetryableCategory, PROVIDER_OBSERVABILITY_TAXONOMY_VERSION, } from "../observability.js";
@@ -18,6 +18,7 @@ import { createScratchpad } from "../runtime/auth-flow.js";
18
18
  import { createProviderCache } from "../runtime/cache.js";
19
19
  import { createProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "../runtime/choice.js";
20
20
  import { createCredentialContext } from "../runtime/credential.js";
21
+ import { createCeremonyEgressLeaseRuntime, ENGINE_CEREMONY_EGRESS_LEASE, } from "../runtime/egress-lease.js";
21
22
  import { createEnvContext } from "../runtime/env.js";
22
23
  import { executeOperation } from "../runtime/executor.js";
23
24
  import { createHttpClient } from "../runtime/http.js";
@@ -27,12 +28,13 @@ import { createOcrClientFromEnv } from "../runtime/ocr.js";
27
28
  import { PROXY_AUTH_IP_DENIED_CODE, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_EXHAUSTED_CODE, } from "../runtime/proxy-errors.js";
28
29
  import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector, } from "../runtime/proxy-telemetry.js";
29
30
  import { closedEnum, RequestTelemetry, tenantOpaqueKeys, } from "../runtime/request-telemetry.js";
30
- import { createUnsupportedResolverClient, } from "../runtime/resolver-shared.js";
31
+ import { ResolverTelemetryCollector, } from "../runtime/resolver-telemetry.js";
32
+ import { createUnsupportedResolverClient, bindResolverTelemetry, inheritResolverTelemetryBinding, } from "../runtime/resolver-shared.js";
31
33
  import { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRET_CODE, } from "../runtime/secrets.js";
32
34
  import { createProviderRuntimeStateFromEnv, createUnsupportedProviderRuntimeState, } from "../runtime/state.js";
33
35
  import { StealthCookieJar } from "../runtime/stealth-cookies.js";
34
36
  import { createSttClientFromEnv } from "../runtime/stt.js";
35
- import { createTraceContext, updateTraceContextExportMetadata, } from "../runtime/trace.js";
37
+ import { createTraceContext, getTraceRecorder, updateTraceContextExportMetadata, } from "../runtime/trace.js";
36
38
  import { resolveTraceConfigFromEnv } from "../runtime/trace-config.js";
37
39
  import { parseSchema } from "../schema.js";
38
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";
@@ -268,6 +270,9 @@ function createLazyStealthClient(onCleanupError, ...createArgs) {
268
270
  async fetch(...args) {
269
271
  return (await getSession()).fetch(...args);
270
272
  },
273
+ async replayChallenged(...args) {
274
+ return (await getSession()).replayChallenged(...args);
275
+ },
271
276
  redirects: {
272
277
  async run(...args) {
273
278
  return (await getSession()).redirects.run(...args);
@@ -304,13 +309,15 @@ function createLazyStealthClient(onCleanupError, ...createArgs) {
304
309
  function bindResolverSignalWithoutRuntime(resolver, defaultSignal) {
305
310
  if (!defaultSignal)
306
311
  return resolver;
307
- return {
312
+ const boundResolver = {
308
313
  solve(challenge, signal = defaultSignal, traceRecorder) {
309
314
  // Forward the instrumentation trace recorder so resolver.vendor.* spans
310
315
  // survive this wrapper. See ResolverSolveWithRecorder in resolver-shared.ts.
311
316
  return resolver.solve(challenge, signal, traceRecorder);
312
317
  },
313
318
  };
319
+ inheritResolverTelemetryBinding(resolver, boundResolver);
320
+ return boundResolver;
314
321
  }
315
322
  function getProviderStealthBaseUrl(provider) {
316
323
  const baseUrl = getProviderBaseUrl(provider);
@@ -323,6 +330,73 @@ function getProviderStealthBaseUrl(provider) {
323
330
  function getProviderStealthProfile(provider) {
324
331
  return provider.stealth ? getStealthProfile(provider.stealth) : undefined;
325
332
  }
333
+ /** One option set for ctx.resolver and the automatic SBSD solve, so both share identity scope, cache, proxy intent, and telemetry. */
334
+ function createResolverRuntimeOptions(provider, cache, identityScope, proxyPolicy, proxyClientOptions, stealthProfile, telemetry) {
335
+ return {
336
+ allowedHosts: provider.allowedHosts,
337
+ cache,
338
+ telemetry,
339
+ identityScope,
340
+ ...(proxyPolicy
341
+ ? {
342
+ proxyIntent: {
343
+ mode: proxyPolicy.mode,
344
+ ...proxyClientOptions,
345
+ ...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
346
+ },
347
+ }
348
+ : {}),
349
+ };
350
+ }
351
+ /** Shared predicate for the SBSD challenge runtime and the ceremony egress lease. */
352
+ function declaresAkamaiSbsdResolver(provider) {
353
+ return provider.resolver?.kinds.some((kind) => kind === "akamai_sbsd") === true;
354
+ }
355
+ function createStealthChallengeDetection(provider, resolverRuntime, resolverOverride, resolverOptions, trace, signal) {
356
+ const resolverDeclared = declaresAkamaiSbsdResolver(provider);
357
+ const detectOnly = provider.stealth?.challengeDetection?.akamaiSbsd === true;
358
+ if (!resolverDeclared && !detectOnly) {
359
+ return undefined;
360
+ }
361
+ return {
362
+ akamaiSbsd: {
363
+ allowedHosts: provider.allowedHosts ?? [],
364
+ // The profile belongs to the SBSD resolver; a resolver declared for other kinds
365
+ // may name a different browser without affecting detect-only classification.
366
+ ...(resolverDeclared && provider.resolver?.clientProfile
367
+ ? { clientProfile: provider.resolver.clientProfile }
368
+ : {}),
369
+ ...(resolverDeclared && resolverRuntime
370
+ ? {
371
+ async solve(challenge, transport, solveSignal) {
372
+ const resolver = resolverRuntime.bindResolverSignal((resolverOverride
373
+ ? bindResolverTelemetry(resolverOverride, resolverOptions.telemetry)
374
+ : undefined) ??
375
+ resolverRuntime.createResolverClientFromEnv(provider.resolver, undefined, {
376
+ allowedHosts: resolverOptions.allowedHosts,
377
+ cache: resolverOptions.cache,
378
+ telemetry: resolverOptions.telemetry,
379
+ identityScope: resolverOptions.identityScope,
380
+ // No proxyIntent: the transport is already bound to the initiating
381
+ // request's lease, and a second identity resolution would allocate
382
+ // again (and fail closed without allocator credentials) for nothing
383
+ // the SBSD adapters consume.
384
+ createTransport: () => transport,
385
+ }), signal);
386
+ // Same span and recorder threading as the instrumented ctx.resolver.solve.
387
+ const recorder = getTraceRecorder(trace);
388
+ const solve = () => resolver.solve(challenge, solveSignal, recorder ?? undefined);
389
+ return recorder
390
+ ? recorder.runSpan("resolver.solve", solve, {
391
+ attributes: { challenge_kind: challenge.kind },
392
+ })
393
+ : solve();
394
+ },
395
+ }
396
+ : {}),
397
+ },
398
+ };
399
+ }
326
400
  function isProductionProviderBrowserMode(provider, env = process.env) {
327
401
  if (provider.runtime !== "browser") {
328
402
  return false;
@@ -424,16 +498,28 @@ function createProviderContext(provider, request, operationId, options, state =
424
498
  engineCredentials: engineProxyCredentials,
425
499
  };
426
500
  const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
501
+ const cache = createProviderCache({ providerId: provider.id });
427
502
  let wrappedContext;
503
+ const { capabilityModules } = options;
504
+ const resolverOptions = createResolverRuntimeOptions(provider, cache, resolverIdentityScope, proxyPolicy, proxyClientOptions, stealthProfile, scope.resolverTelemetry);
505
+ const challengeRuntime = createStealthChallengeDetection(provider, capabilityModules.resolver, options.resolver, resolverOptions, scope.trace, signal);
428
506
  const stealthClientOptions = {
429
507
  upstream: proxyClientOptions.upstream,
430
508
  affinityKey: proxyClientOptions.affinityKey,
431
509
  telemetry: scope.telemetry.proxy,
432
510
  engineCredentials: engineProxyCredentials,
433
511
  ...(signal ? { signal } : {}),
434
- ...(provider.stealth ? { stealth: provider.stealth } : {}),
512
+ // A declared resolver without a `stealth` block still gets the default Chrome client
513
+ // (auth FlowContext is not capability-gated), so the runtime must ride along.
514
+ ...(provider.stealth || challengeRuntime
515
+ ? {
516
+ stealth: {
517
+ ...(provider.stealth ?? {}),
518
+ ...(challengeRuntime ? { challengeRuntime } : {}),
519
+ },
520
+ }
521
+ : {}),
435
522
  };
436
- const { capabilityModules } = options;
437
523
  const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error);
438
524
  const env = createEnvContext([
439
525
  ...providerSecretNames(provider),
@@ -450,7 +536,6 @@ function createProviderContext(provider, request, operationId, options, state =
450
536
  headers: request.headers ?? {},
451
537
  };
452
538
  const requestState = state.forConnection(requestContext.connectionId);
453
- const cache = createProviderCache({ providerId: provider.id });
454
539
  const bindings = {
455
540
  env,
456
541
  credential,
@@ -499,22 +584,13 @@ function createProviderContext(provider, request, operationId, options, state =
499
584
  ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
500
585
  stt: options.stt ?? createSttClientFromEnv(provider.stt),
501
586
  resolver: capabilityModules.resolver
502
- ? capabilityModules.resolver.bindResolverSignal(options.resolver ??
503
- capabilityModules.resolver.createResolverClientFromEnv(provider.resolver, undefined, {
504
- allowedHosts: provider.allowedHosts,
505
- cache,
506
- identityScope: resolverIdentityScope,
507
- ...(proxyPolicy
508
- ? {
509
- proxyIntent: {
510
- mode: proxyPolicy.mode,
511
- ...proxyClientOptions,
512
- ...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
513
- },
514
- }
515
- : {}),
516
- }), signal)
517
- : bindResolverSignalWithoutRuntime(options.resolver ??
587
+ ? capabilityModules.resolver.bindResolverSignal((options.resolver
588
+ ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
589
+ : undefined) ??
590
+ capabilityModules.resolver.createResolverClientFromEnv(provider.resolver, undefined, resolverOptions), signal)
591
+ : bindResolverSignalWithoutRuntime((options.resolver
592
+ ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
593
+ : undefined) ??
518
594
  createUnsupportedResolverClient("Provider does not declare resolver capability"), signal),
519
595
  choice: createProviderChoiceContext({
520
596
  providerId: provider.id,
@@ -564,7 +640,7 @@ export function resolveAuthFlowProxyAffinityKey(provider, request) {
564
640
  request.providerId ??
565
641
  provider.id);
566
642
  }
567
- function createAuthFlowContext(provider, request, options, state, scope, signal) {
643
+ function createAuthFlowContext(provider, request, route, options, state, scope, signal) {
568
644
  const baseUrl = getProviderBaseUrl(provider);
569
645
  const stealthBaseUrl = getProviderStealthBaseUrl(provider);
570
646
  const stealthProfile = getProviderStealthProfile(provider);
@@ -578,16 +654,49 @@ function createAuthFlowContext(provider, request, options, state, scope, signal)
578
654
  telemetry: scope.telemetry.proxy,
579
655
  engineCredentials: engineProxyCredentials,
580
656
  };
657
+ // The lease pins the proxy egress a challenged ceremony solved on, so only providers
658
+ // that declare the akamai_sbsd resolver kind carry one (same predicate as the challenge
659
+ // runtime). `abort` never evaluates the key or tenant: a user must be able to cancel a
660
+ // stranded flow even when engine provisioning is broken, and an abort is not a
661
+ // continuation of the ceremony the lease pins.
662
+ const ceremonyEgressLease = route !== "abort" &&
663
+ declaresAkamaiSbsdResolver(provider) &&
664
+ proxyPolicy &&
665
+ proxyPolicy.mode !== "disabled"
666
+ ? createCeremonyEgressLeaseRuntime({
667
+ tenantId: request.tenantId,
668
+ providerId: provider.id,
669
+ flowId: request.flowId,
670
+ affinityKey: proxyClientOptions.affinityKey,
671
+ ...(request.engine?.egressLease ? { handle: request.engine.egressLease } : {}),
672
+ })
673
+ : undefined;
674
+ if (route !== "abort" && request.engine?.egressLease && !ceremonyEgressLease) {
675
+ throw new SDKError("An egress lease was supplied for an auth flow that does not use a ceremony lease", { code: "EGRESS_LEASE_INVALID" });
676
+ }
581
677
  const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
678
+ const cache = createProviderCache({ providerId: provider.id });
679
+ const { capabilityModules } = options;
680
+ const resolverOptions = createResolverRuntimeOptions(provider, cache, resolverIdentityScope, proxyPolicy, proxyClientOptions, stealthProfile, scope.resolverTelemetry);
681
+ const challengeRuntime = createStealthChallengeDetection(provider, capabilityModules.resolver, options.resolver, resolverOptions, scope.trace, signal);
582
682
  const stealthClientOptions = {
583
683
  upstream: proxyClientOptions.upstream,
584
684
  affinityKey: proxyClientOptions.affinityKey,
585
685
  telemetry: scope.telemetry.proxy,
586
686
  engineCredentials: engineProxyCredentials,
687
+ ...(ceremonyEgressLease ? { [ENGINE_CEREMONY_EGRESS_LEASE]: ceremonyEgressLease } : {}),
587
688
  ...(signal ? { signal } : {}),
588
- ...(provider.stealth ? { stealth: provider.stealth } : {}),
689
+ // A declared resolver without a `stealth` block still gets the default Chrome client
690
+ // (auth FlowContext is not capability-gated), so the runtime must ride along.
691
+ ...(provider.stealth || challengeRuntime
692
+ ? {
693
+ stealth: {
694
+ ...(provider.stealth ?? {}),
695
+ ...(challengeRuntime ? { challengeRuntime } : {}),
696
+ },
697
+ }
698
+ : {}),
589
699
  };
590
- const { capabilityModules } = options;
591
700
  const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "auth", "flow", request.requestId, "stealth", error);
592
701
  const credential = request.connection
593
702
  ? createCredentialContext({
@@ -597,7 +706,6 @@ function createAuthFlowContext(provider, request, options, state, scope, signal)
597
706
  values: request.connection.secrets,
598
707
  })
599
708
  : undefined;
600
- const cache = createProviderCache({ providerId: provider.id });
601
709
  const context = wrapWithInstrumentation({
602
710
  flowId: request.flowId,
603
711
  connectionId: resolveOperationConnectionId(request),
@@ -636,28 +744,23 @@ function createAuthFlowContext(provider, request, options, state, scope, signal)
636
744
  ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
637
745
  stt: options.stt ?? createSttClientFromEnv(provider.stt),
638
746
  resolver: capabilityModules.resolver
639
- ? capabilityModules.resolver.bindResolverSignal(options.resolver ??
640
- capabilityModules.resolver.createResolverClientFromEnv(provider.resolver, undefined, {
641
- allowedHosts: provider.allowedHosts,
642
- cache,
643
- identityScope: resolverIdentityScope,
644
- ...(proxyPolicy
645
- ? {
646
- proxyIntent: {
647
- mode: proxyPolicy.mode,
648
- ...proxyClientOptions,
649
- ...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
650
- },
651
- }
652
- : {}),
653
- }), signal)
654
- : bindResolverSignalWithoutRuntime(options.resolver ??
747
+ ? capabilityModules.resolver.bindResolverSignal((options.resolver
748
+ ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
749
+ : undefined) ??
750
+ capabilityModules.resolver.createResolverClientFromEnv(provider.resolver, undefined, resolverOptions), signal)
751
+ : bindResolverSignalWithoutRuntime((options.resolver
752
+ ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
753
+ : undefined) ??
655
754
  createUnsupportedResolverClient("Provider does not declare resolver capability"), signal),
656
755
  auth: createAuthFlowHelpers({ signal }),
657
756
  });
658
757
  return {
659
758
  context,
660
759
  getPatch: flowContextStore.getPatch,
760
+ getEngineState() {
761
+ const egressLease = ceremonyEgressLease?.handle();
762
+ return egressLease ? { egressLease } : undefined;
763
+ },
661
764
  };
662
765
  }
663
766
  const defaultProviderServerLogger = (event) => {
@@ -1199,8 +1302,10 @@ function createRequestScope(input) {
1199
1302
  })
1200
1303
  : createTraceContext();
1201
1304
  const proxyCollector = new ProxyTelemetryCollector();
1305
+ const resolverCollector = new ResolverTelemetryCollector();
1202
1306
  const telemetry = new RequestTelemetry(trace);
1203
1307
  telemetry.register(proxyCollector);
1308
+ telemetry.register(resolverCollector);
1204
1309
  let rootRunner = (fn) => fn();
1205
1310
  let resolveRoot;
1206
1311
  const rootTerminal = new Promise((resolve) => {
@@ -1275,6 +1380,7 @@ function createRequestScope(input) {
1275
1380
  const scope = {
1276
1381
  trace,
1277
1382
  telemetry,
1383
+ resolverTelemetry: resolverCollector,
1278
1384
  enrich(enrichment) {
1279
1385
  if (terminalOutcome)
1280
1386
  return;
@@ -1384,14 +1490,34 @@ function createRequestScope(input) {
1384
1490
  };
1385
1491
  return scope;
1386
1492
  }
1387
- function responseWithRequestScopeHeaders(response, finished) {
1493
+ // A raw `Response` result skips `toErrorResponse`, so its body stays
1494
+ // provider-owned, but the observability header is still SDK-owned: derive it
1495
+ // from the status. Only timeouts, rate limits, and 5xx are retryable;
1496
+ // `isRetryableCategory` would otherwise mark every other 4xx (`upstream_http`)
1497
+ // as retryable.
1498
+ function rawResponseErrorObservability(status) {
1499
+ return {
1500
+ category: categoryForStatus(status),
1501
+ taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1502
+ retryable: status === 408 || status === 429 || status >= 500,
1503
+ };
1504
+ }
1505
+ // `rawStatusFallback` is decided by the caller from the outcome, not from a
1506
+ // missing snapshot: a failed outcome whose thrown value is `undefined` also has
1507
+ // no `errorObservability`, and its 500 carries the SDK envelope, not a raw body.
1508
+ function responseWithRequestScopeHeaders(response, finished, rawStatusFallback) {
1388
1509
  const headers = new Headers(response.headers);
1389
1510
  headers.delete(PROVIDER_TELEMETRY_HEADER);
1390
1511
  if (finished.providerTelemetryHeader) {
1391
1512
  headers.set(PROVIDER_TELEMETRY_HEADER, finished.providerTelemetryHeader);
1392
1513
  }
1393
- if (finished.errorObservability) {
1394
- headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(finished.errorObservability));
1514
+ headers.delete(ERROR_OBSERVABILITY_HEADER);
1515
+ const errorObservability = finished.errorObservability ??
1516
+ (rawStatusFallback && response.status >= 400
1517
+ ? rawResponseErrorObservability(response.status)
1518
+ : undefined);
1519
+ if (errorObservability) {
1520
+ headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservability));
1395
1521
  }
1396
1522
  return new Response(response.body, {
1397
1523
  headers,
@@ -1402,7 +1528,7 @@ function responseWithRequestScopeHeaders(response, finished) {
1402
1528
  function finalizeRequestResponse(scope, response, outcome) {
1403
1529
  try {
1404
1530
  const error = outcome.kind === "failed" ? outcome.error : undefined;
1405
- const finalResponse = responseWithRequestScopeHeaders(response, scope.snapshotHeaders(error));
1531
+ const finalResponse = responseWithRequestScopeHeaders(response, scope.snapshotHeaders(error), outcome.kind === "completed");
1406
1532
  scope.terminalize(outcome);
1407
1533
  return finalResponse;
1408
1534
  }
@@ -1679,7 +1805,7 @@ function getSseTransport(operation) {
1679
1805
  function getHttpStreamTransport(operation) {
1680
1806
  return operation.transport?.kind === "http-stream" ? operation.transport : undefined;
1681
1807
  }
1682
- function toAuthFlowResponse(result, contextPatch) {
1808
+ function toAuthFlowResponse(result, contextPatch, engine) {
1683
1809
  if (result instanceof Response) {
1684
1810
  return result;
1685
1811
  }
@@ -1689,6 +1815,7 @@ function toAuthFlowResponse(result, contextPatch) {
1689
1815
  return {
1690
1816
  data: result,
1691
1817
  ...(contextPatch ? { contextPatch } : {}),
1818
+ ...(engine ? { engine } : {}),
1692
1819
  };
1693
1820
  }
1694
1821
  function authFlowLocaleFromHeaders(headers) {
@@ -1823,8 +1950,9 @@ async function handleAuthFlow(provider, request, route, options, state, scope, s
1823
1950
  // depend on declared secrets (client ids/secrets), so fail structured before
1824
1951
  // any flow code runs instead of at whatever point the ceremony first reads
1825
1952
  // the env. `abort` stays exempt: a user must always be able to cancel a
1826
- // stranded flow even when provisioning is broken.
1827
- const { context, getPatch } = createAuthFlowContext(provider, request, options, state, scope, signal);
1953
+ // stranded flow even when provisioning is broken (createAuthFlowContext
1954
+ // applies the same exemption to the engine ceremony lease).
1955
+ const { context, getPatch, getEngineState } = createAuthFlowContext(provider, request, route, options, state, scope, signal);
1828
1956
  try {
1829
1957
  if (route !== "abort") {
1830
1958
  assertRequiredSecretsPresent(provider, context.env);
@@ -1855,11 +1983,11 @@ async function handleAuthFlow(provider, request, route, options, state, scope, s
1855
1983
  isAuthTurn(result)
1856
1984
  ? materializeAuthFlowTurn(provider, request, result)
1857
1985
  : result;
1858
- return toAuthFlowResponse(materializedResult, getPatch());
1986
+ return toAuthFlowResponse(materializedResult, getPatch(), getEngineState());
1859
1987
  }
1860
1988
  catch (error) {
1861
1989
  if (error instanceof AuthAbortError) {
1862
- return toAuthFlowResponse(error.turn, getPatch());
1990
+ return toAuthFlowResponse(error.turn, getPatch(), getEngineState());
1863
1991
  }
1864
1992
  throw error;
1865
1993
  }
@@ -2228,7 +2356,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2228
2356
  const handled = await handleOperation(provider, body, operation, options, state, requestScope, c.req.raw.signal);
2229
2357
  const response = handled instanceof Response ? handled : c.json(handled);
2230
2358
  return streaming
2231
- ? responseWithRequestScopeHeaders(response, requestScope.snapshotHeaders())
2359
+ ? responseWithRequestScopeHeaders(response, requestScope.snapshotHeaders(), true)
2232
2360
  : response;
2233
2361
  };
2234
2362
  const response = streaming
@@ -115,6 +115,9 @@ export declare const AuthFlowRequestSchema: z.ZodObject<{
115
115
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
116
116
  input: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
117
117
  context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
118
+ engine: z.ZodOptional<z.ZodObject<{
119
+ egressLease: z.ZodOptional<z.ZodString>;
120
+ }, z.core.$strict>>;
118
121
  connection: z.ZodOptional<z.ZodObject<{
119
122
  id: z.ZodString;
120
123
  mode: z.ZodEnum<{
@@ -133,6 +136,9 @@ export declare const AuthFlowRequestSchema: z.ZodObject<{
133
136
  export declare const AuthFlowSuccessResponseSchema: z.ZodObject<{
134
137
  data: z.ZodUnknown;
135
138
  contextPatch: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodUnknown>>>;
139
+ engine: z.ZodOptional<z.ZodObject<{
140
+ egressLease: z.ZodOptional<z.ZodString>;
141
+ }, z.core.$strict>>;
136
142
  }, z.core.$strip>;
137
143
  export declare const AuthFlowErrorResponseSchema: z.ZodObject<{
138
144
  error: z.ZodObject<{
@@ -85,10 +85,24 @@ export const AuthFlowRequestSchema = z.object({
85
85
  headers: z.record(z.string(), z.string()).optional(),
86
86
  input: z.record(z.string(), z.unknown()).optional(),
87
87
  context: z.record(z.string(), z.unknown()).optional(),
88
+ /** Opaque engine state. This is never projected into FlowContext or provider contextPatch. */
89
+ engine: z
90
+ .object({
91
+ egressLease: z.string().min(1).max(16_384).optional(),
92
+ })
93
+ .strict()
94
+ .optional(),
88
95
  connection: OperationConnectionSchema.optional(),
89
96
  });
90
97
  export const AuthFlowSuccessResponseSchema = z.object({
91
98
  data: z.unknown(),
92
99
  contextPatch: z.record(z.string(), z.unknown().nullable()).optional(),
100
+ /** Opaque engine state to carry verbatim to the next auth turn. */
101
+ engine: z
102
+ .object({
103
+ egressLease: z.string().min(1).max(16_384).optional(),
104
+ })
105
+ .strict()
106
+ .optional(),
93
107
  });
94
108
  export const AuthFlowErrorResponseSchema = OperationErrorResponseSchema;
@@ -1,4 +1,4 @@
1
- import type { StealthProfile, StealthProfileDescriptor, StealthProfileSelection } from "../types.js";
1
+ import type { StealthBrowser, StealthProfile, StealthProfileDescriptor, StealthProfileSelection } from "../types.js";
2
2
  export declare const DEFAULT_STEALTH_BROWSER: "chrome";
3
3
  export declare const DEFAULT_STEALTH_OS: "macos";
4
4
  /** Internal detector used to keep version-pinned string failures actionable. */
@@ -6,4 +6,10 @@ export declare function getVersionPinnedStealthReplacement(name: string): string
6
6
  /** Resolve omitted axes and validate that the resulting pair is supported. */
7
7
  export declare function resolveStealthProfileSelection(selection: StealthProfileSelection | undefined, base?: StealthProfileDescriptor): StealthProfileDescriptor;
8
8
  export declare function getStealthProfile(selection?: StealthProfileSelection): StealthProfile;
9
+ /**
10
+ * The transport owns the browser version (Chromium follows the pinned wreq-js build), so a
11
+ * resolver `clientProfile` such as `safari17_0` or `chrome_149` only has to name the same
12
+ * browser family as the stealth session it runs on.
13
+ */
14
+ export declare function resolverClientProfileFamily(clientProfile: string): StealthBrowser | undefined;
9
15
  export declare function listStealthProfiles(): StealthProfileDescriptor[];