@apifuse/provider-sdk 2.2.0-beta.30 → 2.2.0-beta.32

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.
@@ -4,6 +4,7 @@ import { ProviderError } from "../errors.js";
4
4
  import { getStealthProfile } from "../stealth/profiles.js";
5
5
  import { resolverChallengeAllowsDirectCache, resolverChallengeIsCacheable, resolverChallengeIsIdentityScoped, resolverChallengeIssuingIdentity, } from "./resolver-vendors/bindings.js";
6
6
  import { createBrowserResolverVendorAdapter } from "./resolver-vendors/browser.js";
7
+ import { createCapsolverResolverVendorAdapter } from "./resolver-vendors/capsolver.js";
7
8
  import { assertResolverHostAllowed } from "./resolver-vendors/hosts.js";
8
9
  import { createTwoCaptchaResolverVendorAdapter } from "./resolver-vendors/twocaptcha.js";
9
10
  import { RESOLVER_VENDOR_CAPABILITIES, ResolverVendorUnavailableError, resolverVendorSupports, } from "./resolver-vendors/types.js";
@@ -61,6 +62,13 @@ const resolverAdapterRegistry = {
61
62
  timeoutMs,
62
63
  });
63
64
  },
65
+ capsolver(configuration, timeoutMs, allowedHosts) {
66
+ return createCapsolverResolverVendorAdapter({
67
+ allowedHosts,
68
+ apiKey: configuration,
69
+ timeoutMs,
70
+ });
71
+ },
64
72
  browser(configuration, timeoutMs, allowedHosts) {
65
73
  return createBrowserResolverVendorAdapter({
66
74
  allowedHosts,
@@ -104,7 +112,6 @@ export function swapResolverDefaultUserAgentForTests(resolver) {
104
112
  // This is the sole allowlist for declared vendors whose registry entry may be absent.
105
113
  // Remove a vendor here when its adapter is registered.
106
114
  const KNOWN_UNIMPLEMENTED_RESOLVER_VENDORS = new Set([
107
- "capsolver",
108
115
  "capmonster",
109
116
  "custom",
110
117
  ]);
@@ -11,7 +11,7 @@ import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog.j
11
11
  import { categoryForStatus, sourceForCategory, isRetryableCategory, PROVIDER_OBSERVABILITY_TAXONOMY_VERSION, } from "../observability.js";
12
12
  import { createScratchpad } from "../runtime/auth-flow.js";
13
13
  import { createProviderCache } from "../runtime/cache.js";
14
- import { createProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "../runtime/choice.js";
14
+ import { createProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV, } from "../runtime/choice.js";
15
15
  import { createCredentialContext } from "../runtime/credential.js";
16
16
  import { createEnvContext } from "../runtime/env.js";
17
17
  import { executeOperation } from "../runtime/executor.js";
@@ -376,6 +376,7 @@ function createProviderContext(provider, request, operationId, options, state =
376
376
  const env = createEnvContext([
377
377
  ...(provider.secrets?.map((secret) => secret.name) ?? []),
378
378
  PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
379
+ PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV,
379
380
  ]);
380
381
  const credential = createCredentialContext({
381
382
  allowedKeys: provider.credential?.keys,
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.2.0-beta.30",
2
+ "version": "2.2.0-beta.32",
3
3
  "name": "@apifuse/provider-sdk",
4
4
  "private": false,
5
5
  "type": "module",
@@ -20,6 +20,7 @@ export const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
20
20
  "RUNTIME_UNSUPPORTED",
21
21
  "PROVIDER_STATE_UNSUPPORTED",
22
22
  "CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
23
+ "CHOICE_WORD_ISSUANCE_INVALID",
23
24
  "CHOICE_STATE_PAYLOAD_TOO_LARGE",
24
25
  "CHOICE_STATE_UNAVAILABLE",
25
26
  "CHOICE_CONTEXT_REQUIRED",
package/src/index.ts CHANGED
@@ -61,6 +61,7 @@ export {
61
61
  createProviderChoiceContext,
62
62
  createTestProviderChoiceContext,
63
63
  PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
64
+ PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV,
64
65
  } from "./runtime/choice.js";
65
66
  export {
66
67
  type CreateCredentialContextOptions,
package/src/provider.ts CHANGED
@@ -56,6 +56,7 @@ export {
56
56
  createProviderChoiceContext,
57
57
  createTestProviderChoiceContext,
58
58
  PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
59
+ PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV,
59
60
  } from "./runtime/choice.js";
60
61
  export {
61
62
  APIFUSE_DESCRIPTION_KEY_META_KEY,
@@ -39,6 +39,8 @@ import type {
39
39
 
40
40
  export const PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV =
41
41
  "APIFUSE__PROVIDER_RUNTIME__CHOICE_TOKEN_MASTER_SECRET";
42
+ export const PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV =
43
+ "APIFUSE__PROVIDER_RUNTIME__CHOICE_WORD_ISSUANCE";
42
44
 
43
45
  const PRIMARY_CHOICE_TOKEN_KID = "v1";
44
46
  const MANAGED_CHOICE_TOKEN_VERSION = 1;
@@ -131,28 +133,54 @@ export function createProviderChoiceContext(
131
133
  const issuedAtMs = issueOptions.nowMs ?? Date.now();
132
134
  const resolvedStorage = resolveIssueStorage(issueOptions.storage, issueOptions.payload);
133
135
  if (resolvedStorage.mode === "server") {
136
+ const issuance = resolveChoiceWordIssuance(options.env);
137
+ const keys = hasRequestedChoiceBinding(issueOptions.bind)
138
+ ? deriveManagedChoiceKeys({
139
+ masterSecret: resolveMasterSecret(),
140
+ providerId: options.providerId,
141
+ purpose: issueOptions.purpose,
142
+ kid,
143
+ })
144
+ : undefined;
134
145
  const binding = hasRequestedChoiceBinding(issueOptions.bind)
135
146
  ? createChoiceBinding({
136
- keys: deriveManagedChoiceKeys({
137
- masterSecret: resolveMasterSecret(),
138
- providerId: options.providerId,
139
- purpose: issueOptions.purpose,
140
- kid,
141
- }),
147
+ keys: keys!,
142
148
  options: issueOptions.bind,
143
149
  request: options.request,
144
150
  credential: options.credential,
145
151
  required: true,
146
152
  })
147
153
  : undefined;
148
- return issueServerStoredChoice({
154
+ const baseEnvelope = {
155
+ v: MANAGED_CHOICE_TOKEN_VERSION,
156
+ provider_id: options.providerId,
157
+ purpose: issueOptions.purpose,
158
+ issued_at_ms: issuedAtMs,
159
+ ttl_ms: issueOptions.ttlMs,
160
+ binding,
161
+ } satisfies Omit<ManagedChoiceEnvelope, "payload">;
162
+ if (issuance === "legacy") {
163
+ const legacyKeys =
164
+ keys ??
165
+ deriveManagedChoiceKeys({
166
+ masterSecret: resolveMasterSecret(),
167
+ providerId: options.providerId,
168
+ purpose: issueOptions.purpose,
169
+ kid,
170
+ });
171
+ return issueLegacyServerStoredChoice({
172
+ baseEnvelope,
173
+ issueOptions,
174
+ storage: resolvedStorage.storage,
175
+ contextState: options.state,
176
+ kid,
177
+ keys: legacyKeys,
178
+ issuedAtMs,
179
+ });
180
+ }
181
+ return issueWordServerStoredChoice({
149
182
  baseEnvelope: {
150
- v: MANAGED_CHOICE_TOKEN_VERSION,
151
- provider_id: options.providerId,
152
- purpose: issueOptions.purpose,
153
- issued_at_ms: issuedAtMs,
154
- ttl_ms: issueOptions.ttlMs,
155
- binding,
183
+ ...baseEnvelope,
156
184
  },
157
185
  issueOptions,
158
186
  storage: resolvedStorage.storage,
@@ -233,8 +261,8 @@ export function createProviderChoiceContext(
233
261
  masterSecret: resolveMasterSecret(),
234
262
  providerId: options.providerId,
235
263
  purpose: parseOptions.purpose,
236
- kid,
237
- }),
264
+ kid,
265
+ }),
238
266
  onConsume: (result) =>
239
267
  emitChoiceTelemetry(options.onTelemetry, {
240
268
  providerId: options.providerId,
@@ -247,15 +275,16 @@ export function createProviderChoiceContext(
247
275
  replay: result.status === "already-consumed",
248
276
  }),
249
277
  });
250
- return observeChoiceParse<
251
- ProviderChoiceTokenPayload | ProviderChoiceExplicitParseResult
252
- >(parsed, {
253
- onTelemetry: options.onTelemetry,
254
- providerId: options.providerId,
255
- purpose: parseOptions.purpose,
256
- format: "word",
257
- consumeMode,
258
- });
278
+ return observeChoiceParse<ProviderChoiceTokenPayload | ProviderChoiceExplicitParseResult>(
279
+ parsed,
280
+ {
281
+ onTelemetry: options.onTelemetry,
282
+ providerId: options.providerId,
283
+ purpose: parseOptions.purpose,
284
+ format: "word",
285
+ consumeMode,
286
+ },
287
+ );
259
288
  }
260
289
 
261
290
  // Legacy encrypted-envelope compatibility fallback. Removal is gated on
@@ -285,13 +314,9 @@ export function createProviderChoiceContext(
285
314
  purpose: parseOptions.purpose,
286
315
  kid: tokenKid,
287
316
  });
288
- const signedBody = [
289
- parseOptions.prefix,
290
- tokenKid,
291
- encodedIv,
292
- encryptedPayload,
293
- authTag,
294
- ].join(".");
317
+ const signedBody = [parseOptions.prefix, tokenKid, encodedIv, encryptedPayload, authTag].join(
318
+ ".",
319
+ );
295
320
  assertManagedChoiceSignature({
296
321
  signedBody,
297
322
  signature,
@@ -347,15 +372,16 @@ export function createProviderChoiceContext(
347
372
  }),
348
373
  )
349
374
  : payload;
350
- return observeChoiceParse<
351
- ProviderChoiceTokenPayload | ProviderChoiceExplicitParseResult
352
- >(parsed, {
353
- onTelemetry: options.onTelemetry,
354
- providerId: options.providerId,
355
- purpose: parseOptions.purpose,
356
- format: "legacy",
357
- consumeMode,
358
- });
375
+ return observeChoiceParse<ProviderChoiceTokenPayload | ProviderChoiceExplicitParseResult>(
376
+ parsed,
377
+ {
378
+ onTelemetry: options.onTelemetry,
379
+ providerId: options.providerId,
380
+ purpose: parseOptions.purpose,
381
+ format: "legacy",
382
+ consumeMode,
383
+ },
384
+ );
359
385
  } catch (error) {
360
386
  emitChoiceParseFailure(options.onTelemetry, error, {
361
387
  providerId: options.providerId,
@@ -485,6 +511,22 @@ function createLegacyExplicitParseResult(options: {
485
511
  };
486
512
  }
487
513
 
514
+ function resolveChoiceWordIssuance(env?: EnvContext): "legacy" | "word" {
515
+ const configured = env?.get(PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV);
516
+ const value = configured?.trim() ?? "";
517
+ if (value === "" || value === "legacy") return "legacy";
518
+ if (value === "word") return "word";
519
+ throw new ProviderError(
520
+ `Unsupported provider choice word issuance mode "${value}". Expected "legacy" or "word".`,
521
+ {
522
+ code: "CHOICE_WORD_ISSUANCE_INVALID",
523
+ category: "input_validation",
524
+ retryable: false,
525
+ details: { env: PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV },
526
+ },
527
+ );
528
+ }
529
+
488
530
  function resolveChoiceMasterSecret(options: CreateProviderChoiceContextOptions): string {
489
531
  const configured =
490
532
  options.masterSecret ?? options.env?.get(PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV);
@@ -559,7 +601,7 @@ function encryptManagedChoiceToken(options: {
559
601
  return `${signedBody}.${signature}`;
560
602
  }
561
603
 
562
- async function issueServerStoredChoice<TPayload extends ProviderChoiceTokenPayload>(options: {
604
+ async function issueWordServerStoredChoice<TPayload extends ProviderChoiceTokenPayload>(options: {
563
605
  readonly baseEnvelope: Omit<ManagedChoiceEnvelope, "payload">;
564
606
  readonly issueOptions: ProviderChoiceIssueOptions<TPayload>;
565
607
  readonly storage: ServerProviderChoiceStorageOptions;
@@ -615,6 +657,56 @@ async function issueServerStoredChoice<TPayload extends ProviderChoiceTokenPaylo
615
657
  });
616
658
  }
617
659
 
660
+ /**
661
+ * Beta.28-compatible server handle issuance. The state stores the payload and
662
+ * the client receives the existing six-part encrypted envelope whose payload
663
+ * is a server-state handle.
664
+ */
665
+ async function issueLegacyServerStoredChoice<TPayload extends ProviderChoiceTokenPayload>(options: {
666
+ readonly baseEnvelope: Omit<ManagedChoiceEnvelope, "payload">;
667
+ readonly issueOptions: ProviderChoiceIssueOptions<TPayload>;
668
+ readonly storage: ServerProviderChoiceStorageOptions;
669
+ readonly contextState?: ProviderRuntimeState;
670
+ readonly kid: string;
671
+ readonly keys: ManagedChoiceKeys;
672
+ readonly issuedAtMs: number;
673
+ }): Promise<string> {
674
+ const serializedPayload = serializeChoicePayload(options.issueOptions.payload);
675
+ const payloadBytes = Buffer.byteLength(serializedPayload, "utf8");
676
+ if (payloadBytes > options.storage.maxValueBytes) {
677
+ throw new ProviderError("Provider choice payload exceeds state storage policy.", {
678
+ code: "CHOICE_STATE_PAYLOAD_TOO_LARGE",
679
+ category: "input_validation",
680
+ retryable: false,
681
+ details: { maxValueBytes: options.storage.maxValueBytes, payloadBytes },
682
+ });
683
+ }
684
+ const stateId = `choice_${randomBytes(16).toString("base64url")}`;
685
+ const namespace = resolveChoiceStateNamespace({
686
+ storage: options.storage,
687
+ contextState: options.contextState,
688
+ ttlMs: options.issueOptions.ttlMs,
689
+ });
690
+ await namespace.set(optionsStateKey(stateId), options.issueOptions.payload, {
691
+ ttl: stateTtl(options.storage, options.issueOptions.ttlMs),
692
+ });
693
+ const envelope: ManagedChoiceEnvelope = {
694
+ ...options.baseEnvelope,
695
+ payload: {
696
+ storage: "server",
697
+ state_id: stateId,
698
+ payload_digest: digestChoicePayload(serializedPayload),
699
+ created_at_ms: options.issuedAtMs,
700
+ },
701
+ };
702
+ return encryptManagedChoiceToken({
703
+ prefix: options.issueOptions.prefix,
704
+ kid: options.kid,
705
+ envelope,
706
+ keys: options.keys,
707
+ });
708
+ }
709
+
618
710
  async function parseWordServerStoredChoice(options: {
619
711
  readonly stateKey: string;
620
712
  readonly parseOptions: ProviderChoiceParseOptions;