@apifuse/provider-sdk 2.2.0-beta.47 → 2.2.0-beta.48

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.48
4
+
5
+ - Release candidate for main commit ba6636fa8dd53a35af3091ae6efd797f5ef3d836.
6
+
3
7
  ## 2.2.0-beta.47
4
8
 
5
9
  - Release candidate for main commit 0ff0c6c90153af3aab75978eb73040c9adaa2609.
@@ -1 +1 @@
1
- export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, type ResolverAdapterFactory, type ResolverInstrumentationMetadata, type ResolverRuntimeOptions, } from "./resolver.js";
1
+ export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, getResolverSolutionSource, invalidateCachedResolverSolution, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, type ResolverAdapterFactory, type ResolverInstrumentationMetadata, type ResolverRuntimeOptions, type ResolverSolutionSource, } from "./resolver.js";
@@ -1 +1 @@
1
- export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, } from "./resolver.js";
1
+ export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, getResolverSolutionSource, invalidateCachedResolverSolution, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, } from "./resolver.js";
@@ -7,6 +7,11 @@ const CAPSOLVER_VENDOR_ID = "capsolver";
7
7
  const DEFAULT_CAPSOLVER_BASE_URL = "https://api.capsolver.com";
8
8
  const DEFAULT_POLL_INTERVAL_MS = 2_000;
9
9
  const DEFAULT_TIMEOUT_MS = 120_000;
10
+ const SDK_ESTIMATED_COOKIE_TTL_MS_BY_CHALLENGE_KIND = {
11
+ // CapSolver omits expiry, so use one conservative hour despite measured
12
+ // AWS WAF lifetimes of days.
13
+ aws_waf: 60 * 60 * 1_000,
14
+ };
10
15
  class CapsolverSolveTimeoutError extends Error {
11
16
  constructor() {
12
17
  super("Capsolver resolver solve budget elapsed");
@@ -483,6 +488,7 @@ export function createCapsolverResolverVendorAdapter(options) {
483
488
  form: "cookies",
484
489
  cookies: { "aws-waf-token": solutionValue },
485
490
  userAgent: identity?.userAgent ?? getStealthProfile(DEFAULT_PROFILE).userAgent,
491
+ sdkEstimatedExpires: (now() + SDK_ESTIMATED_COOKIE_TTL_MS_BY_CHALLENGE_KIND.aws_waf) / 1_000,
486
492
  }
487
493
  : { form: "token", token: solutionValue };
488
494
  }
@@ -365,6 +365,7 @@ export function createTwoCaptchaResolverVendorAdapter(options) {
365
365
  if (!token?.trim()) {
366
366
  throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "transport_failure", { phase });
367
367
  }
368
+ // AWS WAF remains a token solution here, so resolver cookie caching does not apply.
368
369
  return { form: "token", token };
369
370
  }
370
371
  };
@@ -31,6 +31,7 @@ export interface ResolverRuntimeOptions {
31
31
  readonly identityScope?: string;
32
32
  }) => ResolverVendorTransport;
33
33
  }
34
+ export type ResolverSolutionSource = "cache" | "vendor";
34
35
  export type ResolverInstrumentationMetadata = {
35
36
  readonly target: ResolverContext;
36
37
  readonly traceRecorder: TraceRecorder;
@@ -40,8 +41,19 @@ export declare const RESOLVER_ADAPTER_REGISTRY: Partial<Readonly<Record<Provider
40
41
  export declare function swapResolverAdapterFactoryForTests(vendor: ProviderResolverVendor, factory: ResolverAdapterFactory | undefined): () => void;
41
42
  /** Internal test seam; deliberately not re-exported from the package root. */
42
43
  export declare function swapResolverDefaultUserAgentForTests(resolver: (() => string | undefined) | undefined): () => void;
44
+ /**
45
+ * Identify whether this exact SDK-returned solution object came from the cache
46
+ * or a vendor solve. Returns undefined for copied or caller-created objects.
47
+ */
48
+ export declare function getResolverSolutionSource(solution: ChallengeSolution): ResolverSolutionSource | undefined;
43
49
  /** Remove the cached entry for the exact solution object returned by this resolver. */
44
50
  export declare function invalidateResolverSolution(resolver: ResolverContext, challenge: ProviderChallenge, solution: ChallengeSolution): Promise<void>;
51
+ /**
52
+ * Remove a solution only when it was returned from the resolver cache. Providers
53
+ * should call this when an upstream serves the same challenge after a cached
54
+ * solution was applied, so the next `solve()` mints a fresh solution.
55
+ */
56
+ export declare function invalidateCachedResolverSolution(resolver: ResolverContext, challenge: ProviderChallenge, solution: ChallengeSolution): Promise<boolean>;
45
57
  export declare function createResolverClient(options: {
46
58
  readonly kinds: readonly ProviderChallengeKind[];
47
59
  readonly adapters: readonly ResolverVendorAdapter[];
@@ -19,6 +19,7 @@ const RESOLVER_SOLUTION_INDEX_CACHE_NAMESPACE = "resolver-solution-index";
19
19
  const MIN_RESOLVER_CACHE_TTL_MS = 1_000;
20
20
  const resolverCaches = new WeakMap();
21
21
  const solutionIssuerDigests = new WeakMap();
22
+ const resolverSolutionSources = new WeakMap();
22
23
  const SAFE_CAUSE_MESSAGE_WORDS = new Set([
23
24
  "abort",
24
25
  "aborted",
@@ -376,7 +377,7 @@ function isResolverCacheIndex(value) {
376
377
  function solutionExpiryMs(solution) {
377
378
  if (solution.form !== "cookies")
378
379
  return undefined;
379
- const expires = solution.expires;
380
+ const expires = solution.expires ?? solution.sdkEstimatedExpires;
380
381
  if (typeof expires !== "number" || !Number.isFinite(expires))
381
382
  return undefined;
382
383
  return expires * 1_000;
@@ -386,14 +387,33 @@ function rememberSolutionIssuer(solution, issuerDigest) {
386
387
  solutionIssuerDigests.set(solution, issuerDigest);
387
388
  }
388
389
  }
390
+ function rememberSolutionSource(solution, source) {
391
+ if (typeof solution === "object" && solution !== null) {
392
+ resolverSolutionSources.set(solution, source);
393
+ }
394
+ }
395
+ /**
396
+ * Identify whether this exact SDK-returned solution object came from the cache
397
+ * or a vendor solve. Returns undefined for copied or caller-created objects.
398
+ */
399
+ export function getResolverSolutionSource(solution) {
400
+ return resolverSolutionSources.get(solution);
401
+ }
389
402
  async function readCachedSolution(cache, challenge, issuerDigest, now) {
390
403
  const cached = await cache.get(resolverSolutionCacheKey(cache, challenge, issuerDigest));
391
404
  if (!cached || !isCachedResolverSolution(cached.value))
392
405
  return undefined;
393
406
  if (cached.value.issuerDigest !== issuerDigest || cached.value.expiresAtMs <= now)
394
407
  return undefined;
395
- rememberSolutionIssuer(cached.value.solution, issuerDigest);
396
- return cached.value.solution;
408
+ if (cached.value.solution.form !== "cookies")
409
+ return undefined;
410
+ const solution = {
411
+ ...cached.value.solution,
412
+ cookies: { ...cached.value.solution.cookies },
413
+ };
414
+ rememberSolutionIssuer(solution, issuerDigest);
415
+ rememberSolutionSource(solution, "cache");
416
+ return solution;
397
417
  }
398
418
  async function findCachedSolution(cache, challenge, identity, identityScope) {
399
419
  const now = Date.now();
@@ -457,8 +477,7 @@ async function cacheResolverSolution(cache, challenge, solution, identity, ident
457
477
  { direct: true, expiresAtMs, issuerDigest },
458
478
  ], now);
459
479
  }
460
- /** Remove the cached entry for the exact solution object returned by this resolver. */
461
- export async function invalidateResolverSolution(resolver, challenge, solution) {
480
+ async function invalidateResolverSolutionWithOutcome(resolver, challenge, solution) {
462
481
  const metadata = resolver[RESOLVER_INSTRUMENTATION_METADATA];
463
482
  const cacheOwner = metadata?.target ?? resolver;
464
483
  const invalidate = async () => {
@@ -484,14 +503,28 @@ export async function invalidateResolverSolution(resolver, challenge, solution)
484
503
  return "index_entry_deleted";
485
504
  };
486
505
  if (!metadata) {
487
- await invalidate();
488
- return;
506
+ return await invalidate();
489
507
  }
490
- await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
508
+ return await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
491
509
  attributes: { challenge_kind: challenge.kind },
492
510
  onSuccess: (outcome) => ({ outcome }),
493
511
  });
494
512
  }
513
+ /** Remove the cached entry for the exact solution object returned by this resolver. */
514
+ export async function invalidateResolverSolution(resolver, challenge, solution) {
515
+ await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
516
+ }
517
+ /**
518
+ * Remove a solution only when it was returned from the resolver cache. Providers
519
+ * should call this when an upstream serves the same challenge after a cached
520
+ * solution was applied, so the next `solve()` mints a fresh solution.
521
+ */
522
+ export async function invalidateCachedResolverSolution(resolver, challenge, solution) {
523
+ if (getResolverSolutionSource(solution) !== "cache")
524
+ return false;
525
+ const outcome = await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
526
+ return outcome === "entry_deleted" || outcome === "index_entry_deleted";
527
+ }
495
528
  async function resolveResolverIdentity(proxyIntent) {
496
529
  const userAgentSource = proxyIntent.userAgent ? "declared" : "defaulted";
497
530
  let proxyUrl;
@@ -618,6 +651,7 @@ function createResolverChainClient(options) {
618
651
  await cacheResolverSolution(options.cache, challenge, solution, issuingIdentity, options.identityScope);
619
652
  }
620
653
  }
654
+ rememberSolutionSource(solution, "vendor");
621
655
  return solution;
622
656
  }
623
657
  catch (error) {
package/dist/types.d.ts CHANGED
@@ -338,6 +338,11 @@ export type ChallengeSolution = {
338
338
  readonly userAgent: string;
339
339
  /** Epoch seconds copied from the upstream cookie's own expiry attribute; never a constant. */
340
340
  readonly expires?: number;
341
+ /**
342
+ * Epoch seconds conservatively estimated by the SDK when a resolver vendor omits
343
+ * the upstream cookie's expiry. `expires` takes precedence when both are present.
344
+ */
345
+ readonly sdkEstimatedExpires?: number;
341
346
  };
342
347
  export interface ProviderResolverConfig {
343
348
  /** Optional ordered override for the SDK-owned vendor fallback chain. */
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.2.0-beta.47",
2
+ "version": "2.2.0-beta.48",
3
3
  "name": "@apifuse/provider-sdk",
4
4
  "private": false,
5
5
  "type": "module",
@@ -10,6 +10,8 @@ export {
10
10
  createUnsupportedResolverClient,
11
11
  DEFAULT_RESOLVER_VENDOR_PREFERENCE,
12
12
  DEFAULT_RESOLVER_TIMEOUT_MS,
13
+ getResolverSolutionSource,
14
+ invalidateCachedResolverSolution,
13
15
  invalidateResolverSolution,
14
16
  RESOLVER_ADAPTER_REGISTRY,
15
17
  RESOLVER_INSTRUMENTATION_METADATA,
@@ -17,4 +19,5 @@ export {
17
19
  type ResolverAdapterFactory,
18
20
  type ResolverInstrumentationMetadata,
19
21
  type ResolverRuntimeOptions,
22
+ type ResolverSolutionSource,
20
23
  } from "./resolver.js";
@@ -1,5 +1,5 @@
1
1
  import { getStealthProfile } from "../../stealth/profiles.js";
2
- import type { ChallengeSolution, ProviderChallenge } from "../../types.js";
2
+ import type { ChallengeSolution, ProviderChallenge, ProviderChallengeKind } from "../../types.js";
3
3
  import { redactSensitiveText } from "../request-options.js";
4
4
  import { DEFAULT_PROFILE } from "../stealth.js";
5
5
  import type { TraceRecorder } from "../trace.js";
@@ -17,6 +17,11 @@ const CAPSOLVER_VENDOR_ID = "capsolver" as const;
17
17
  const DEFAULT_CAPSOLVER_BASE_URL = "https://api.capsolver.com";
18
18
  const DEFAULT_POLL_INTERVAL_MS = 2_000;
19
19
  const DEFAULT_TIMEOUT_MS = 120_000;
20
+ const SDK_ESTIMATED_COOKIE_TTL_MS_BY_CHALLENGE_KIND = {
21
+ // CapSolver omits expiry, so use one conservative hour despite measured
22
+ // AWS WAF lifetimes of days.
23
+ aws_waf: 60 * 60 * 1_000,
24
+ } satisfies Partial<Record<ProviderChallengeKind, number>>;
20
25
 
21
26
  type Delay = (ms: number, signal: AbortSignal) => Promise<void>;
22
27
  type CapsolverOperationPhase = "create_task" | "poll_result";
@@ -657,6 +662,8 @@ export function createCapsolverResolverVendorAdapter(
657
662
  form: "cookies" as const,
658
663
  cookies: { "aws-waf-token": solutionValue },
659
664
  userAgent: identity?.userAgent ?? getStealthProfile(DEFAULT_PROFILE).userAgent,
665
+ sdkEstimatedExpires:
666
+ (now() + SDK_ESTIMATED_COOKIE_TTL_MS_BY_CHALLENGE_KIND.aws_waf) / 1_000,
660
667
  }
661
668
  : { form: "token" as const, token: solutionValue };
662
669
  }
@@ -497,6 +497,7 @@ export function createTwoCaptchaResolverVendorAdapter(
497
497
  { phase },
498
498
  );
499
499
  }
500
+ // AWS WAF remains a token solution here, so resolver cookie caching does not apply.
500
501
  return { form: "token" as const, token };
501
502
  }
502
503
  };
@@ -143,11 +143,21 @@ type ResolverCacheIndex = {
143
143
  }[];
144
144
  };
145
145
 
146
+ export type ResolverSolutionSource = "cache" | "vendor";
147
+
148
+ type ResolverSolutionInvalidationOutcome =
149
+ | "cache_disabled"
150
+ | "entry_deleted"
151
+ | "index_entry_deleted"
152
+ | "not_cookie_solution"
153
+ | "solution_not_cached";
154
+
146
155
  const RESOLVER_SOLUTION_CACHE_NAMESPACE = "resolver-solution";
147
156
  const RESOLVER_SOLUTION_INDEX_CACHE_NAMESPACE = "resolver-solution-index";
148
157
  const MIN_RESOLVER_CACHE_TTL_MS = 1_000;
149
158
  const resolverCaches = new WeakMap<object, ProviderCache | null>();
150
159
  const solutionIssuerDigests = new WeakMap<object, string>();
160
+ const resolverSolutionSources = new WeakMap<object, ResolverSolutionSource>();
151
161
  const SAFE_CAUSE_MESSAGE_WORDS: ReadonlySet<string> = new Set([
152
162
  "abort",
153
163
  "aborted",
@@ -597,7 +607,7 @@ function isResolverCacheIndex(value: unknown): value is ResolverCacheIndex {
597
607
 
598
608
  function solutionExpiryMs(solution: ChallengeSolution): number | undefined {
599
609
  if (solution.form !== "cookies") return undefined;
600
- const expires = solution.expires;
610
+ const expires = solution.expires ?? solution.sdkEstimatedExpires;
601
611
  if (typeof expires !== "number" || !Number.isFinite(expires)) return undefined;
602
612
  return expires * 1_000;
603
613
  }
@@ -608,6 +618,22 @@ function rememberSolutionIssuer(solution: ChallengeSolution, issuerDigest: strin
608
618
  }
609
619
  }
610
620
 
621
+ function rememberSolutionSource(solution: ChallengeSolution, source: ResolverSolutionSource): void {
622
+ if (typeof solution === "object" && solution !== null) {
623
+ resolverSolutionSources.set(solution, source);
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Identify whether this exact SDK-returned solution object came from the cache
629
+ * or a vendor solve. Returns undefined for copied or caller-created objects.
630
+ */
631
+ export function getResolverSolutionSource(
632
+ solution: ChallengeSolution,
633
+ ): ResolverSolutionSource | undefined {
634
+ return resolverSolutionSources.get(solution);
635
+ }
636
+
611
637
  async function readCachedSolution(
612
638
  cache: ProviderCache,
613
639
  challenge: ProviderChallenge,
@@ -618,8 +644,14 @@ async function readCachedSolution(
618
644
  if (!cached || !isCachedResolverSolution(cached.value)) return undefined;
619
645
  if (cached.value.issuerDigest !== issuerDigest || cached.value.expiresAtMs <= now)
620
646
  return undefined;
621
- rememberSolutionIssuer(cached.value.solution, issuerDigest);
622
- return cached.value.solution;
647
+ if (cached.value.solution.form !== "cookies") return undefined;
648
+ const solution = {
649
+ ...cached.value.solution,
650
+ cookies: { ...cached.value.solution.cookies },
651
+ };
652
+ rememberSolutionIssuer(solution, issuerDigest);
653
+ rememberSolutionSource(solution, "cache");
654
+ return solution;
623
655
  }
624
656
 
625
657
  async function findCachedSolution(
@@ -720,25 +752,18 @@ async function cacheResolverSolution(
720
752
  );
721
753
  }
722
754
 
723
- /** Remove the cached entry for the exact solution object returned by this resolver. */
724
- export async function invalidateResolverSolution(
755
+ async function invalidateResolverSolutionWithOutcome(
725
756
  resolver: ResolverContext,
726
757
  challenge: ProviderChallenge,
727
758
  solution: ChallengeSolution,
728
- ): Promise<void> {
759
+ ): Promise<ResolverSolutionInvalidationOutcome> {
729
760
  const metadata = (
730
761
  resolver as ResolverContext & {
731
762
  readonly [RESOLVER_INSTRUMENTATION_METADATA]?: ResolverInstrumentationMetadata;
732
763
  }
733
764
  )[RESOLVER_INSTRUMENTATION_METADATA];
734
765
  const cacheOwner = metadata?.target ?? resolver;
735
- const invalidate = async (): Promise<
736
- | "cache_disabled"
737
- | "entry_deleted"
738
- | "index_entry_deleted"
739
- | "not_cookie_solution"
740
- | "solution_not_cached"
741
- > => {
766
+ const invalidate = async (): Promise<ResolverSolutionInvalidationOutcome> => {
742
767
  if (solution.form !== "cookies") return "not_cookie_solution";
743
768
  if (!resolverCaches.has(cacheOwner)) {
744
769
  throw new Error("Resolver cache registration lookup failed during solution invalidation");
@@ -764,15 +789,38 @@ export async function invalidateResolverSolution(
764
789
  };
765
790
 
766
791
  if (!metadata) {
767
- await invalidate();
768
- return;
792
+ return await invalidate();
769
793
  }
770
- await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
794
+ return await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
771
795
  attributes: { challenge_kind: challenge.kind },
772
796
  onSuccess: (outcome) => ({ outcome }),
773
797
  });
774
798
  }
775
799
 
800
+ /** Remove the cached entry for the exact solution object returned by this resolver. */
801
+ export async function invalidateResolverSolution(
802
+ resolver: ResolverContext,
803
+ challenge: ProviderChallenge,
804
+ solution: ChallengeSolution,
805
+ ): Promise<void> {
806
+ await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
807
+ }
808
+
809
+ /**
810
+ * Remove a solution only when it was returned from the resolver cache. Providers
811
+ * should call this when an upstream serves the same challenge after a cached
812
+ * solution was applied, so the next `solve()` mints a fresh solution.
813
+ */
814
+ export async function invalidateCachedResolverSolution(
815
+ resolver: ResolverContext,
816
+ challenge: ProviderChallenge,
817
+ solution: ChallengeSolution,
818
+ ): Promise<boolean> {
819
+ if (getResolverSolutionSource(solution) !== "cache") return false;
820
+ const outcome = await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
821
+ return outcome === "entry_deleted" || outcome === "index_entry_deleted";
822
+ }
823
+
776
824
  async function resolveResolverIdentity(
777
825
  proxyIntent: NonNullable<ResolverRuntimeOptions["proxyIntent"]>,
778
826
  ): Promise<{
@@ -938,6 +986,7 @@ function createResolverChainClient(options: {
938
986
  );
939
987
  }
940
988
  }
989
+ rememberSolutionSource(solution, "vendor");
941
990
  return solution;
942
991
  } catch (error) {
943
992
  signal.throwIfAborted();
package/src/types.ts CHANGED
@@ -409,6 +409,11 @@ export type ChallengeSolution =
409
409
  readonly userAgent: string;
410
410
  /** Epoch seconds copied from the upstream cookie's own expiry attribute; never a constant. */
411
411
  readonly expires?: number;
412
+ /**
413
+ * Epoch seconds conservatively estimated by the SDK when a resolver vendor omits
414
+ * the upstream cookie's expiry. `expires` takes precedence when both are present.
415
+ */
416
+ readonly sdkEstimatedExpires?: number;
412
417
  };
413
418
 
414
419
  export interface ProviderResolverConfig {