@apifuse/provider-sdk 2.2.0-beta.22 → 2.2.0-beta.24

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 (64) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/apifuse-dev.ts +4 -0
  3. package/bin/apifuse-pack-types.ts +234 -38
  4. package/bin/apifuse-perf.ts +15 -12
  5. package/bin/apifuse-record.ts +4 -0
  6. package/dist/config/loader.d.ts +8 -19
  7. package/dist/config/loader.js +28 -86
  8. package/dist/contract-types.d.ts +1 -0
  9. package/dist/contract.js +2 -0
  10. package/dist/define.d.ts +5 -1
  11. package/dist/define.js +79 -6
  12. package/dist/error-resolution.js +5 -0
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +2 -0
  15. package/dist/provider.d.ts +1 -1
  16. package/dist/runtime/auth-flow.d.ts +2 -1
  17. package/dist/runtime/auth-flow.js +4 -0
  18. package/dist/runtime/browser.js +78 -9
  19. package/dist/runtime/http.js +0 -1
  20. package/dist/runtime/instrumentation.js +26 -1
  21. package/dist/runtime/ocr.d.ts +29 -0
  22. package/dist/runtime/ocr.js +440 -0
  23. package/dist/runtime/resolver-vendors/bindings.d.ts +8 -0
  24. package/dist/runtime/resolver-vendors/bindings.js +15 -0
  25. package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
  26. package/dist/runtime/resolver-vendors/browser.js +287 -0
  27. package/dist/runtime/resolver-vendors/types.d.ts +42 -0
  28. package/dist/runtime/resolver-vendors/types.js +57 -0
  29. package/dist/runtime/resolver.d.ts +39 -0
  30. package/dist/runtime/resolver.js +414 -0
  31. package/dist/runtime/state.d.ts +3 -0
  32. package/dist/runtime/state.js +245 -141
  33. package/dist/runtime/stealth.js +3 -6
  34. package/dist/runtime/stt.js +1 -12
  35. package/dist/runtime/timeout.d.ts +5 -0
  36. package/dist/runtime/timeout.js +12 -0
  37. package/dist/server/serve.d.ts +6 -1
  38. package/dist/server/serve.js +39 -8
  39. package/dist/testing/run.js +10 -0
  40. package/dist/types.d.ts +163 -4
  41. package/package.json +1 -1
  42. package/src/config/loader.ts +35 -111
  43. package/src/contract-types.ts +1 -0
  44. package/src/contract.ts +2 -0
  45. package/src/define.ts +121 -7
  46. package/src/error-resolution.ts +5 -0
  47. package/src/index.ts +45 -1
  48. package/src/provider.ts +1 -0
  49. package/src/runtime/auth-flow.ts +6 -0
  50. package/src/runtime/browser.ts +139 -19
  51. package/src/runtime/http.ts +0 -1
  52. package/src/runtime/instrumentation.ts +36 -2
  53. package/src/runtime/ocr.ts +523 -0
  54. package/src/runtime/resolver-vendors/bindings.ts +31 -0
  55. package/src/runtime/resolver-vendors/browser.ts +420 -0
  56. package/src/runtime/resolver-vendors/types.ts +113 -0
  57. package/src/runtime/resolver.ts +668 -0
  58. package/src/runtime/state.ts +323 -166
  59. package/src/runtime/stealth.ts +3 -6
  60. package/src/runtime/stt.ts +1 -19
  61. package/src/runtime/timeout.ts +18 -0
  62. package/src/server/serve.ts +80 -5
  63. package/src/testing/run.ts +15 -0
  64. package/src/types.ts +188 -4
@@ -787,7 +787,6 @@ function createSessionFetcher(
787
787
  const resolvedProxy = await resolveProxyConfigAsync({
788
788
  proxy: options?.proxy ?? clientOptions.proxy,
789
789
  upstream: clientOptions.upstream,
790
- apifuseConfig: clientOptions.apifuseConfig,
791
790
  affinityKey: clientOptions.affinityKey,
792
791
  proxyAttempt: computeProxyAttemptIndex({
793
792
  baseProxyAttempt: clientOptions.proxyAttempt,
@@ -860,11 +859,9 @@ function createSessionFetcher(
860
859
  // A registry vendor chain (smartproxy/nodemaven) is the only policy whose
861
860
  // successive attempts resolve a *different* endpoint, so it is the only one
862
861
  // that may widen the attempt cap to the pool span, de-duplicate endpoints,
863
- // and drive allocator stale-pool refresh. A static custom/decodo policy
864
- // resolves the same URL every attempt: widening/refreshing it would resend
865
- // the request dozens of times (up to maxAttempts × refreshes) and bypass
866
- // retry:false and unsafe-method controls. Static policies therefore follow
867
- // the ordinary transport-retry budget instead.
862
+ // and drive allocator stale-pool refresh. Deprecated custom/decodo policies
863
+ // have no managed endpoint to rotate or refresh, so they follow the ordinary
864
+ // transport-retry budget instead.
868
865
  const rotatesRegistryChain =
869
866
  usesPolicyAllocator && policyResolvesRegistryVendorChain(policyProxy);
870
867
  const maxAttempts = rotatesRegistryChain ? policyProxyAttemptCap : retryAttemptCap;
@@ -14,6 +14,7 @@ import type {
14
14
  VerificationCodeCandidateSource,
15
15
  VerificationCodeExtractionResult,
16
16
  } from "../types.js";
17
+ import { createTimeoutController, isTimeoutLikeError } from "./timeout.js";
17
18
 
18
19
  export const APIFUSE__STT__BACKEND_ENV = "APIFUSE__STT__BACKEND";
19
20
  export const APIFUSE__STT__MODEL_ENV = "APIFUSE__STT__MODEL";
@@ -169,15 +170,6 @@ function normalizeCloudflareLanguage(language: Bcp47Locale | undefined): string
169
170
  return language?.split("-")[0]?.toLowerCase();
170
171
  }
171
172
 
172
- function isTimeoutLikeError(error: unknown): error is Error {
173
- return (
174
- error instanceof Error &&
175
- (error.name === "AbortError" ||
176
- error.name === "TimeoutError" ||
177
- /\b(timed out|timeout|deadline exceeded)\b/i.test(error.message))
178
- );
179
- }
180
-
181
173
  function toSttTransportError(error: unknown): TransportError {
182
174
  if (error instanceof TransportError) return error;
183
175
  if (isTimeoutLikeError(error)) {
@@ -194,16 +186,6 @@ function toSttTransportError(error: unknown): TransportError {
194
186
  });
195
187
  }
196
188
 
197
- function createTimeoutController(signalTimeoutMs: number): {
198
- controller: AbortController;
199
- clear: () => void;
200
- } {
201
- const controller = new AbortController();
202
- const timeout = setTimeout(() => controller.abort(), signalTimeoutMs);
203
- timeout.unref?.();
204
- return { controller, clear: () => clearTimeout(timeout) };
205
- }
206
-
207
189
  function toCloudflareInput(request: SttTranscribeRequest): Record<string, unknown> {
208
190
  const prompt = resolveSttPrompt(request);
209
191
  const input: Record<string, unknown> = {
@@ -0,0 +1,18 @@
1
+ export function isTimeoutLikeError(error: unknown): error is Error {
2
+ return (
3
+ error instanceof Error &&
4
+ (error.name === "AbortError" ||
5
+ error.name === "TimeoutError" ||
6
+ /\b(timed out|timeout|deadline exceeded)\b/i.test(error.message))
7
+ );
8
+ }
9
+
10
+ export function createTimeoutController(signalTimeoutMs: number): {
11
+ controller: AbortController;
12
+ clear: () => void;
13
+ } {
14
+ const controller = new AbortController();
15
+ const timeout = setTimeout(() => controller.abort(), signalTimeoutMs);
16
+ timeout.unref?.();
17
+ return { controller, clear: () => clearTimeout(timeout) };
18
+ }
@@ -48,12 +48,14 @@ import {
48
48
  createNativeNetworkClient,
49
49
  } from "../runtime/native-network.js";
50
50
  import { getProviderBaseUrl } from "../runtime/provider.js";
51
+ import { createOcrClientFromEnv } from "../runtime/ocr.js";
51
52
  import {
52
53
  PROXY_AUTH_IP_DENIED_CODE,
53
54
  PROXY_EDGE_AUTH_REJECTED_CODE,
54
55
  PROXY_POOL_EXHAUSTED_CODE,
55
56
  } from "../runtime/proxy-errors.js";
56
57
  import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector } from "../runtime/proxy-telemetry.js";
58
+ import { bindResolverSignal, createResolverClientFromEnv } from "../runtime/resolver.js";
57
59
  import {
58
60
  assertRequiredSecretsPresent,
59
61
  listMissingRequiredSecrets,
@@ -92,12 +94,14 @@ import type {
92
94
  OperationErrorCode,
93
95
  OperationHttpStreamTransport,
94
96
  OperationSseTransport,
97
+ OcrContext,
95
98
  ProviderErrorStatus,
96
99
  ProviderContext,
97
100
  ProviderDefinition,
98
101
  ProviderProxyPolicy,
99
102
  ProviderRuntimeState,
100
103
  ProviderStreamEvent,
104
+ ResolverContext,
101
105
  StealthClient,
102
106
  SttContext,
103
107
  } from "../types.js";
@@ -298,6 +302,18 @@ export function resolveProviderProxyAffinityKey(
298
302
  return connectionKey ?? provider.id;
299
303
  }
300
304
 
305
+ export function resolveProviderResolverIdentityScope(
306
+ provider: ProviderDefinition,
307
+ affinityKey: string,
308
+ contextId: string,
309
+ ): string {
310
+ return JSON.stringify({
311
+ proxy: provider.proxy ?? null,
312
+ affinityKey,
313
+ contextId,
314
+ });
315
+ }
316
+
301
317
  function resolveOperationConnectionId(request: OperationRequest): string | undefined {
302
318
  return request.connection?.id ?? request.connectionId;
303
319
  }
@@ -316,6 +332,7 @@ function createProviderContext(
316
332
  options: ProviderServerOptions = {},
317
333
  state: ProviderRuntimeState = createUnsupportedProviderRuntimeState(),
318
334
  proxyTelemetry?: ProxyTelemetryCollector,
335
+ signal?: AbortSignal,
319
336
  ): ProviderContext {
320
337
  const baseUrl = getProviderBaseUrl(provider);
321
338
  const stealthBaseUrl = getProviderStealthBaseUrl(provider);
@@ -325,6 +342,11 @@ function createProviderContext(
325
342
  affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
326
343
  telemetry: proxyTelemetry,
327
344
  };
345
+ const resolverIdentityScope = resolveProviderResolverIdentityScope(
346
+ provider,
347
+ proxyClientOptions.affinityKey,
348
+ request.requestId,
349
+ );
328
350
  let wrappedContext: ProviderContext | undefined;
329
351
  const stealthClientOptions = {
330
352
  upstream: proxyClientOptions.upstream,
@@ -346,6 +368,8 @@ function createProviderContext(
346
368
  connectionId: resolveOperationConnectionId(request),
347
369
  headers: request.headers ?? {},
348
370
  };
371
+ const requestState = state.forConnection(requestContext.connectionId);
372
+ const cache = createProviderCache({ providerId: provider.id });
349
373
  const context = wrapWithInstrumentation({
350
374
  env,
351
375
  credential,
@@ -357,8 +381,8 @@ function createProviderContext(
357
381
  retryResponseMeta.set(wrappedContext, summary);
358
382
  },
359
383
  }),
360
- cache: createProviderCache({ providerId: provider.id }),
361
- state,
384
+ cache,
385
+ state: requestState,
362
386
  stealth: stealthBaseUrl
363
387
  ? stealthProfile
364
388
  ? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
@@ -389,13 +413,23 @@ function createProviderContext(
389
413
  : {}),
390
414
  trace: createTraceContext(),
391
415
  auth: createAuthStub(),
416
+ ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
392
417
  stt: options.stt ?? createSttClientFromEnv(provider.stt),
418
+ resolver: bindResolverSignal(
419
+ options.resolver ??
420
+ createResolverClientFromEnv(provider.resolver, undefined, {
421
+ allowedHosts: provider.allowedHosts,
422
+ cache,
423
+ identityScope: resolverIdentityScope,
424
+ }),
425
+ signal,
426
+ ),
393
427
  choice: createProviderChoiceContext({
394
428
  providerId: provider.id,
395
429
  env,
396
430
  request: requestContext,
397
431
  credential,
398
- state,
432
+ state: requestState,
399
433
  }),
400
434
  });
401
435
  wrappedContext = context;
@@ -464,6 +498,11 @@ function createAuthFlowContext(
464
498
  request.providerId ??
465
499
  provider.id,
466
500
  };
501
+ const resolverIdentityScope = resolveProviderResolverIdentityScope(
502
+ provider,
503
+ proxyClientOptions.affinityKey,
504
+ request.requestId,
505
+ );
467
506
  const stealthClientOptions = {
468
507
  upstream: proxyClientOptions.upstream,
469
508
  affinityKey: proxyClientOptions.affinityKey,
@@ -476,6 +515,7 @@ function createAuthFlowContext(
476
515
  values: request.connection.secrets,
477
516
  })
478
517
  : undefined;
518
+ const cache = createProviderCache({ providerId: provider.id });
479
519
 
480
520
  return {
481
521
  context: {
@@ -512,7 +552,17 @@ function createAuthFlowContext(
512
552
  ]),
513
553
  credential,
514
554
  context: flowContextStore.context,
555
+ ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
515
556
  stt: options.stt ?? createSttClientFromEnv(provider.stt),
557
+ resolver: bindResolverSignal(
558
+ options.resolver ??
559
+ createResolverClientFromEnv(provider.resolver, undefined, {
560
+ allowedHosts: provider.allowedHosts,
561
+ cache,
562
+ identityScope: resolverIdentityScope,
563
+ }),
564
+ signal,
565
+ ),
516
566
  auth: createAuthFlowHelpers({ signal }),
517
567
  },
518
568
  getPatch: flowContextStore.getPatch,
@@ -596,6 +646,10 @@ export type ProviderServerOptions = {
596
646
  };
597
647
  /** Optional STT override for tests or custom hosts; local/prod normally resolves from env. */
598
648
  stt?: SttContext;
649
+ /** Optional OCR override for tests or custom hosts; local/prod normally resolves from env. */
650
+ ocr?: OcrContext;
651
+ /** Optional resolver override for tests or custom hosts; local/prod normally resolves from env. */
652
+ resolver?: ResolverContext;
599
653
  /** Optional runtime state override for tests or custom hosts. Production resolves Redis from env and fails closed when unavailable. */
600
654
  state?: ProviderRuntimeState;
601
655
  /** Allow process-local runtime state only for local development and tests. */
@@ -1511,8 +1565,17 @@ async function handleOperation(
1511
1565
  options: ProviderServerOptions = {},
1512
1566
  state: ProviderRuntimeState = createUnsupportedProviderRuntimeState(),
1513
1567
  proxyTelemetry?: ProxyTelemetryCollector,
1568
+ signal?: AbortSignal,
1514
1569
  ): Promise<Response | OperationResponse> {
1515
- const ctx = createProviderContext(provider, request, operationId, options, state, proxyTelemetry);
1570
+ const ctx = createProviderContext(
1571
+ provider,
1572
+ request,
1573
+ operationId,
1574
+ options,
1575
+ state,
1576
+ proxyTelemetry,
1577
+ signal,
1578
+ );
1516
1579
  const operation = provider.operations[operationId];
1517
1580
  const streaming = operation?.transport?.kind && operation.transport.kind !== "json";
1518
1581
  let cleanupCalled = false;
@@ -1551,6 +1614,7 @@ async function handleOperation(
1551
1614
  operationId,
1552
1615
  ctx,
1553
1616
  request,
1617
+ signal,
1554
1618
  })
1555
1619
  : await executeOperation(provider, operationId, ctx, request.input);
1556
1620
  if (streaming && operation) {
@@ -1937,7 +2001,15 @@ export function createServerApp(
1937
2001
  }
1938
2002
  const request = operationRequestFromForwardingEnvelope(envelope);
1939
2003
  operationId = envelope.operationId;
1940
- const ctx = createProviderContext(provider, request, operationId, options, state);
2004
+ const ctx = createProviderContext(
2005
+ provider,
2006
+ request,
2007
+ operationId,
2008
+ options,
2009
+ state,
2010
+ undefined,
2011
+ signal,
2012
+ );
1941
2013
  if (deadlineAtMs !== undefined && deadlineAtMs <= Date.now()) {
1942
2014
  throw new StatefulRoutingDeadlineError(envelope.requestId, envelope.deadlineAt as string);
1943
2015
  }
@@ -2005,6 +2077,7 @@ export function createServerApp(
2005
2077
  options,
2006
2078
  state,
2007
2079
  proxyTelemetry,
2080
+ c.req.raw.signal,
2008
2081
  );
2009
2082
  if (response instanceof Response) {
2010
2083
  logProviderSuccess(
@@ -2381,7 +2454,9 @@ export async function serve(
2381
2454
 
2382
2455
  const app = createServerApp(provider, {
2383
2456
  logger: options.logger,
2457
+ ocr: options.ocr,
2384
2458
  stt: options.stt,
2459
+ resolver: options.resolver,
2385
2460
  state: options.state,
2386
2461
  allowMemoryStateFallback: options.allowMemoryStateFallback,
2387
2462
  operationExecutor: options.operationExecutor,
@@ -3,6 +3,7 @@ import { describe, expect, it } from "bun:test";
3
3
  import { createProviderCache } from "../runtime/cache.js";
4
4
  import { createTestProviderChoiceContext } from "../runtime/choice.js";
5
5
  import { createMemoryProviderRuntimeState } from "../runtime/state.js";
6
+ import { createUnsupportedOcrClient } from "../runtime/ocr.js";
6
7
  import { createUnsupportedSttClient } from "../runtime/stt.js";
7
8
  import {
8
9
  createNativeEgressAuthorization,
@@ -395,6 +396,8 @@ function createUpstreamContext(
395
396
  },
396
397
  }),
397
398
  close: async () => {},
399
+ cookies: async () =>
400
+ (await browserAction("cookies")).data as Awaited<ReturnType<BrowserPage["cookies"]>>,
398
401
  fill: async (selector, textValue) => {
399
402
  await browserAction("fill", { selector, text: textValue });
400
403
  },
@@ -559,9 +562,15 @@ function createUpstreamContext(
559
562
  : {}),
560
563
  trace: { span: async (_name, fn) => fn() },
561
564
  auth: { requestField: async (name) => unsupported(`ctx.auth.requestField(${name})`) },
565
+ ocr: createUnsupportedOcrClient(
566
+ "Standard test upstream context does not support ctx.ocr.recognize",
567
+ ),
562
568
  stt: createUnsupportedSttClient(
563
569
  "Standard test upstream context does not support ctx.stt.transcribe",
564
570
  ),
571
+ resolver: {
572
+ solve: async () => unsupported("ctx.resolver.solve"),
573
+ },
565
574
  choice: createTestProviderChoiceContext({
566
575
  providerId: `standard-test-${operationName}`,
567
576
  request,
@@ -684,9 +693,15 @@ export function createSnapshotContext(rawFixture: unknown): ProviderContext {
684
693
  auth: {
685
694
  requestField: async (name) => unsupported(`ctx.auth.requestField(${name})`),
686
695
  },
696
+ ocr: createUnsupportedOcrClient(
697
+ "Standard test snapshot context does not support ctx.ocr.recognize",
698
+ ),
687
699
  stt: createUnsupportedSttClient(
688
700
  "Standard test snapshot context does not support ctx.stt.transcribe",
689
701
  ),
702
+ resolver: {
703
+ solve: async () => unsupported("ctx.resolver.solve"),
704
+ },
690
705
  choice: createTestProviderChoiceContext({
691
706
  providerId: "standard-test",
692
707
  request,
package/src/types.ts CHANGED
@@ -243,6 +243,61 @@ export interface SmsOtpMatcherDefinition {
243
243
  extractOtp(body: string): string | null;
244
244
  }
245
245
 
246
+ export interface ProviderOcrConfig {
247
+ readonly mode: "required" | "optional";
248
+ }
249
+
250
+ export type OcrImageInput =
251
+ | { readonly kind: "base64"; readonly data: string; readonly mediaType?: string }
252
+ | { readonly kind: "url"; readonly url: string };
253
+
254
+ export interface OcrRecognizeRequest {
255
+ readonly image: OcrImageInput;
256
+ readonly hint?: "captcha" | "document" | "generic";
257
+ readonly prompt?: string;
258
+ readonly maxTokens?: number;
259
+ readonly timeoutMs?: number;
260
+ }
261
+
262
+ export interface OcrWarning {
263
+ readonly code: string;
264
+ readonly message: string;
265
+ }
266
+
267
+ export interface OcrResult {
268
+ readonly text: string;
269
+ readonly model: string;
270
+ readonly warnings?: readonly OcrWarning[];
271
+ }
272
+
273
+ export interface OcrCaptchaOptions {
274
+ readonly length?: number;
275
+ /** Allowed characters. A RegExp is applied to each character, not to the whole text. */
276
+ readonly charset?: string | RegExp;
277
+ readonly caseSensitive?: boolean;
278
+ readonly maxCandidates?: number;
279
+ }
280
+
281
+ export interface OcrCaptchaCandidate {
282
+ readonly text: string;
283
+ readonly satisfiesConstraints: boolean;
284
+ }
285
+
286
+ export interface OcrCaptchaResult {
287
+ readonly text: string;
288
+ readonly candidates: readonly OcrCaptchaCandidate[];
289
+ readonly satisfiesConstraints: boolean;
290
+ readonly model: string;
291
+ }
292
+
293
+ export interface OcrContext {
294
+ recognize(request: OcrRecognizeRequest): Promise<OcrResult>;
295
+ extractCaptchaText(
296
+ image: OcrImageInput,
297
+ options?: OcrCaptchaOptions,
298
+ ): Promise<OcrCaptchaResult>;
299
+ }
300
+
246
301
  export type SttTranscribeMode = "general" | "otp";
247
302
  export type SttPromptPolicy = "none" | "default-hint" | "custom-hint";
248
303
  export type SttUnsupportedOptionPolicy = "warn" | "error";
@@ -252,6 +307,94 @@ export interface ProviderSttConfig {
252
307
  mode: ProviderSttMode;
253
308
  }
254
309
 
310
+ /**
311
+ * `browser` is the in-house CDP pool (`apps/cdp-pool`, reached through
312
+ * `createBrowserClient`) and is a first-class vendor rather than an escape hatch:
313
+ * for fingerprint-family kinds, it was measured faster than a paid vendor
314
+ * (4.5 s vs 17.5 s) at zero marginal cost.
315
+ *
316
+ * `2captcha` is the vendor already carrying production traffic in
317
+ * `apifuse-provider-tabelog`.
318
+ *
319
+ * Union order is documentation only; the effective fallback order is whatever
320
+ * `ProviderResolverConfig.vendors` declares.
321
+ */
322
+ export type ProviderResolverVendor =
323
+ | "browser"
324
+ | "capsolver"
325
+ | "capmonster"
326
+ | "2captcha"
327
+ | "custom";
328
+
329
+ /**
330
+ * Token-family kinds resolve to `{ form: "token" }`. Cookie-family kinds resolve
331
+ * to `{ form: "cookies" }`; network-identity binding is defined per kind. `aws_waf`
332
+ * was measured portable across residential leases on buyee, while `cf_clearance`
333
+ * remains unmeasured here and is treated as identity-scoped because it is widely
334
+ * described as IP-bound.
335
+ */
336
+ export type ProviderChallenge =
337
+ | {
338
+ readonly kind: "turnstile";
339
+ readonly siteKey: string;
340
+ readonly pageUrl: string;
341
+ readonly action?: string;
342
+ readonly cdata?: string;
343
+ }
344
+ | {
345
+ readonly kind: "recaptcha_v2";
346
+ readonly siteKey: string;
347
+ readonly pageUrl: string;
348
+ }
349
+ | {
350
+ readonly kind: "recaptcha_v3";
351
+ readonly siteKey: string;
352
+ readonly pageUrl: string;
353
+ readonly action: string;
354
+ readonly minScore?: number;
355
+ }
356
+ | {
357
+ readonly kind: "hcaptcha";
358
+ readonly siteKey: string;
359
+ readonly pageUrl: string;
360
+ }
361
+ | {
362
+ readonly kind: "cloudflare_interstitial";
363
+ readonly pageUrl: string;
364
+ readonly blockedHtml?: string;
365
+ }
366
+ | {
367
+ readonly kind: "aws_waf";
368
+ readonly pageUrl: string;
369
+ readonly captchaScript?: string;
370
+ readonly context?: string;
371
+ readonly iv?: string;
372
+ };
373
+
374
+ export type ProviderChallengeKind = ProviderChallenge["kind"];
375
+
376
+ /**
377
+ * Token solutions carry no network-identity binding. Cookie-solution binding is
378
+ * per challenge kind: `aws_waf` was measured portable across residential leases
379
+ * on buyee, while `cf_clearance` is unmeasured here and treated as scoped to the
380
+ * identity that produced it. The provider attaches the returned cookies to its
381
+ * own requests.
382
+ */
383
+ export type ChallengeSolution =
384
+ | { readonly form: "token"; readonly token: string }
385
+ | {
386
+ readonly form: "cookies";
387
+ readonly cookies: Readonly<Record<string, string>>;
388
+ readonly userAgent: string;
389
+ };
390
+
391
+ export interface ProviderResolverConfig {
392
+ /** Ordered vendor fallback chain, tried first to last. */
393
+ readonly vendors: readonly ProviderResolverVendor[];
394
+ /** Challenge kinds this provider is permitted to request. */
395
+ readonly kinds: readonly ProviderChallengeKind[];
396
+ }
397
+
255
398
  export type SttAudioInput = {
256
399
  kind: "base64";
257
400
  data: string;
@@ -330,6 +473,10 @@ export interface SttContext {
330
473
  ): VerificationCodeExtractionResult;
331
474
  }
332
475
 
476
+ export interface ResolverContext {
477
+ solve(challenge: ProviderChallenge, signal?: AbortSignal): Promise<ChallengeSolution>;
478
+ }
479
+
333
480
  export interface HealthJourneySchedule {
334
481
  kind: "interval";
335
482
  /** ISO 8601 duration, for example PT8H. */
@@ -794,10 +941,12 @@ export type ProviderProxyMode = "disabled" | "optional" | "required";
794
941
  * - `decodo` — **decodo.com**, the *gateway* proxy that was named "Smartproxy"
795
942
  * (smartproxy.com) before its 2025 rebrand to Decodo. Sticky sessions via
796
943
  * username params. A different company from `smartproxy` above.
797
- * **@deprecated** — unused; no managed adapter. Use `smartproxy`/`nodemaven`,
798
- * or the `APIFUSE__PROXY__URL` bring-your-own escape hatch.
799
- * - `custom` **@deprecated** bring-your-own static proxy URL marker. The
800
- * `APIFUSE__PROXY__URL` env still works without declaring this value.
944
+ * **@deprecated** — unused; no managed adapter. Declare a
945
+ * `ProviderProxyPolicy` using `smartproxy` or `nodemaven` instead; the
946
+ * `smartproxy` allocator requires `APIFUSE__PROXY__SMARTPROXY_APP_KEY`.
947
+ * - `custom` **@deprecated** static proxy marker with no managed adapter.
948
+ * Use `ProviderProxyPolicy` with `smartproxy`/`nodemaven`; the `smartproxy`
949
+ * allocator requires `APIFUSE__PROXY__SMARTPROXY_APP_KEY`.
801
950
  */
802
951
  export type ProviderProxyProvider = "smartproxy" | "nodemaven" | "decodo" | "custom";
803
952
 
@@ -1563,6 +1712,18 @@ export interface BrowserFrame {
1563
1712
  locator(selector: string): BrowserLocator;
1564
1713
  }
1565
1714
 
1715
+ export interface BrowserCookie {
1716
+ readonly name: string;
1717
+ readonly value: string;
1718
+ readonly domain: string;
1719
+ readonly path: string;
1720
+ /** Unix seconds. Absent for a session cookie. */
1721
+ readonly expires?: number;
1722
+ readonly httpOnly: boolean;
1723
+ readonly secure: boolean;
1724
+ readonly sameSite?: "Strict" | "Lax" | "None";
1725
+ }
1726
+
1566
1727
  export type BrowserResourceMethod = "GET" | "HEAD";
1567
1728
 
1568
1729
  export type BrowserResourceRequest = {
@@ -1604,6 +1765,11 @@ export type BrowserResourcePolicy = {
1604
1765
 
1605
1766
  export interface BrowserPage extends BrowserFrame {
1606
1767
  close(): Promise<void>;
1768
+ /**
1769
+ * Reads the browser context's cookie jar, including httpOnly cookies.
1770
+ * Cookie expiry values are Unix seconds and are absent for session cookies.
1771
+ */
1772
+ cookies(): Promise<readonly BrowserCookie[]>;
1607
1773
  fill(selector: string, text: string): Promise<void>;
1608
1774
  goto(url: string): Promise<void>;
1609
1775
  pageId?: string;
@@ -1889,7 +2055,9 @@ export interface FlowContext {
1889
2055
  env: EnvContext;
1890
2056
  credential?: CredentialContext;
1891
2057
  context: ContextScratchpad;
2058
+ ocr: OcrContext;
1892
2059
  stt: SttContext;
2060
+ resolver: ResolverContext;
1893
2061
  auth: AuthFlowTerminalContext;
1894
2062
  }
1895
2063
 
@@ -1932,7 +2100,14 @@ export type ProviderStateDurationString =
1932
2100
  | `${number}${"ms" | "s" | "m" | "h" | "d"}`
1933
2101
  | `PT${string}`;
1934
2102
 
2103
+ export type StateNamespaceScope = "connection" | "provider";
2104
+
1935
2105
  export interface StateNamespaceOptions {
2106
+ /**
2107
+ * State isolation boundary. Connection scope is the default; provider scope
2108
+ * must be selected explicitly for provider-wide coordination state.
2109
+ */
2110
+ scope?: StateNamespaceScope;
1936
2111
  /** Default TTL used when a write omits ttl. Required to avoid unbounded state. */
1937
2112
  defaultTtl: ProviderStateDurationString;
1938
2113
  /** Maximum allowed TTL; writes are rejected when they exceed this policy. */
@@ -1993,6 +2168,11 @@ export interface ProviderStateNamespace {
1993
2168
  }
1994
2169
 
1995
2170
  export interface ProviderRuntimeState {
2171
+ /**
2172
+ * Returns an immutable view bound to one request connection. An unresolved
2173
+ * connection uses an isolated reserved sentinel, never the provider-global scope.
2174
+ */
2175
+ forConnection(connectionId: string | undefined): ProviderRuntimeState;
1996
2176
  namespace(
1997
2177
  name: string,
1998
2178
  options: StateNamespaceOptions,
@@ -2014,7 +2194,9 @@ export interface ProviderContext {
2014
2194
  browser: BrowserClient;
2015
2195
  trace: TraceContext;
2016
2196
  auth: AuthContext;
2197
+ ocr: OcrContext;
2017
2198
  stt: SttContext;
2199
+ resolver: ResolverContext;
2018
2200
  choice: ProviderChoiceContext;
2019
2201
  }
2020
2202
 
@@ -2187,7 +2369,9 @@ export interface ProviderDefinition {
2187
2369
  platform: StealthPlatform;
2188
2370
  };
2189
2371
  proxy?: ProviderProxyConfig;
2372
+ ocr?: ProviderOcrConfig;
2190
2373
  stt?: ProviderSttConfig;
2374
+ resolver?: ProviderResolverConfig;
2191
2375
  browser?: {
2192
2376
  engine: BrowserEngine;
2193
2377
  };