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

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/src/define.ts CHANGED
@@ -27,6 +27,7 @@ import type {
27
27
  OperationTransport,
28
28
  OperationWebSocketTransport,
29
29
  NativeProviderConfig,
30
+ ProviderOcrConfig,
30
31
  ProviderAccessConfig,
31
32
  ProviderDefinition,
32
33
  ProviderDeploymentOverrides,
@@ -123,6 +124,7 @@ const VALID_PROVIDER_PROXY_AFFINITIES = [
123
124
  "auth-flow",
124
125
  "connection",
125
126
  ] as const;
127
+ const VALID_PROVIDER_OCR_MODES = ["optional", "required"] as const;
126
128
  const VALID_PROVIDER_STT_MODES = ["optional", "required"] as const;
127
129
  const SMARTPROXY_APP_KEY_SECRET = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
128
130
  const NODEMAVEN_USERNAME_SECRET = "APIFUSE__PROXY__NODEMAVEN_USERNAME";
@@ -248,6 +250,7 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
248
250
  platform: StealthPlatform;
249
251
  };
250
252
  proxy?: ProviderProxyConfig;
253
+ ocr?: ProviderOcrConfig;
251
254
  stt?: ProviderSttConfig;
252
255
  browser?: { engine: BrowserEngine };
253
256
  auth?: AuthConfig;
@@ -697,6 +700,18 @@ function validateProviderStt(config: { id: string; stt?: ProviderSttConfig }): v
697
700
  assertLiteralField(stt.mode, "stt.mode", VALID_PROVIDER_STT_MODES, config.id);
698
701
  }
699
702
 
703
+ function validateProviderOcr(config: { id: string; ocr?: ProviderOcrConfig }): void {
704
+ const ocr = config.ocr;
705
+ if (ocr === undefined) return;
706
+ if (!ocr || typeof ocr !== "object" || Array.isArray(ocr)) {
707
+ throw new ValidationError(`Provider "${config.id}" has invalid ocr: must be an object.`, {
708
+ fix: `Use ocr: { mode: "required" } or ocr: { mode: "optional" }.`,
709
+ });
710
+ }
711
+ rejectUnknownFields(ocr, new Set(["mode"]), "ocr");
712
+ assertLiteralField(ocr.mode, "ocr.mode", VALID_PROVIDER_OCR_MODES, config.id);
713
+ }
714
+
700
715
  function validateOperationIds(
701
716
  providerId: string,
702
717
  operations: Record<string, ProviderOperation>,
@@ -2386,6 +2401,7 @@ export function defineProvider<
2386
2401
  throw error;
2387
2402
  }
2388
2403
  validateProviderProxy(config);
2404
+ validateProviderOcr(config);
2389
2405
  validateProviderStt(config);
2390
2406
  if (config.runtime === "browser" && !config.browser)
2391
2407
  throw new ProviderError(
@@ -2410,6 +2426,7 @@ export function defineProvider<
2410
2426
  native: config.native,
2411
2427
  stealth: config.stealth,
2412
2428
  proxy: config.proxy,
2429
+ ocr: config.ocr,
2413
2430
  stt: config.stt,
2414
2431
  browser: config.browser,
2415
2432
  auth: config.auth,
@@ -30,6 +30,7 @@ export const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
30
30
  "flow_expired",
31
31
  "turn_validation_error",
32
32
  "context_access_error",
33
+ "OCR_UPSTREAM_FAILED",
33
34
  "UNSUPPORTED_STT_OPTION",
34
35
  "INVALID_STT_AUDIO",
35
36
  "STT_AUDIO_TOO_LARGE",
@@ -85,6 +86,8 @@ export const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
85
86
  export const SDK_RUNTIME_OWNED_ERROR_CODES = new Set([
86
87
  ...SDK_OWNED_PROVIDER_ERROR_CODES,
87
88
  "reauth_required",
89
+ "OCR_UNAVAILABLE",
90
+ "UNSUPPORTED_OCR_BACKEND",
88
91
  "STT_UNAVAILABLE",
89
92
  "UNSUPPORTED_STT_BACKEND",
90
93
  "OUTPUT_VALIDATION_FAILED",
@@ -116,6 +119,8 @@ export const SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES: ReadonlyMap<string, Provide
116
119
  ["UPSTREAM_REJECTED", 409],
117
120
  ["UPSTREAM_ERROR", 502],
118
121
  ["BLOCKED", 502],
122
+ ["OCR_UNAVAILABLE", 503],
123
+ ["UNSUPPORTED_OCR_BACKEND", 503],
119
124
  ["STT_UNAVAILABLE", 503],
120
125
  ["UNSUPPORTED_STT_BACKEND", 503],
121
126
  ["STATEFUL_FORWARDING_REPLAY_CACHE_FULL", 503],
package/src/index.ts CHANGED
@@ -111,6 +111,22 @@ export {
111
111
  UnsupportedProviderStateError,
112
112
  } from "./runtime/state.js";
113
113
  export { createStealthClient } from "./runtime/stealth.js";
114
+ export {
115
+ APIFUSE__OCR__API_KEY_ENV,
116
+ APIFUSE__OCR__BACKEND_ENV,
117
+ APIFUSE__OCR__BASE_URL_ENV,
118
+ APIFUSE__OCR__CLOUDFLARE_API_TOKEN_ENV,
119
+ APIFUSE__OCR__MODEL_ENV,
120
+ CLOUDFLARE_ACCOUNT_ID_ENV,
121
+ CLOUDFLARE_WORKERS_AI_OCR_BACKEND,
122
+ createCloudflareWorkersAiOcrClient,
123
+ createOcrClientFromEnv,
124
+ createOpenAiCompatibleOcrClient,
125
+ createUnsupportedOcrClient,
126
+ DEFAULT_CLOUDFLARE_WORKERS_AI_OCR_MODEL,
127
+ extractCaptchaCandidates,
128
+ OPENAI_COMPATIBLE_OCR_BACKEND,
129
+ } from "./runtime/ocr.js";
114
130
  export {
115
131
  APIFUSE__STT__BACKEND_ENV,
116
132
  APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV,
@@ -229,6 +245,14 @@ export type {
229
245
  NativeTcpPortRange,
230
246
  NativeTcpTlsMode,
231
247
  NativeTlsConnectOptions,
248
+ OcrCaptchaCandidate,
249
+ OcrCaptchaOptions,
250
+ OcrCaptchaResult,
251
+ OcrContext,
252
+ OcrImageInput,
253
+ OcrRecognizeRequest,
254
+ OcrResult,
255
+ OcrWarning,
232
256
  OperationAnnotations,
233
257
  OperationApprovalPolicy,
234
258
  OperationContractMetadata,
@@ -274,6 +298,7 @@ export type {
274
298
  ProviderLogoProfile,
275
299
  ProviderLogoSource,
276
300
  ProviderMeta,
301
+ ProviderOcrConfig,
277
302
  ProviderProxyConfig,
278
303
  ProviderProxyMode,
279
304
  ProviderProxyPolicy,
@@ -5,9 +5,11 @@ import type {
5
5
  EnvContext,
6
6
  FlowContext,
7
7
  HttpClient,
8
+ OcrContext,
8
9
  StealthClient,
9
10
  SttContext,
10
11
  } from "../types.js";
12
+ import { createUnsupportedOcrClient } from "./ocr.js";
11
13
  import { createUnsupportedSttClient } from "./stt.js";
12
14
 
13
15
  function normalizeAllowedKeys(allowedKeys: string[]): Set<string> {
@@ -58,6 +60,7 @@ export function createFlowContext(options: {
58
60
  externalRef?: string;
59
61
  allowedKeys: string[];
60
62
  initialContext?: Record<string, unknown>;
63
+ ocr?: OcrContext;
61
64
  stt?: SttContext;
62
65
  }): FlowContext {
63
66
  return {
@@ -70,6 +73,7 @@ export function createFlowContext(options: {
70
73
  stealth: options.stealth,
71
74
  env: options.env,
72
75
  context: createScratchpad(options.allowedKeys, options.initialContext),
76
+ ocr: options.ocr ?? createUnsupportedOcrClient(),
73
77
  stt: options.stt ?? createUnsupportedSttClient(),
74
78
  auth: createAuthFlowHelpers(),
75
79
  };
@@ -42,19 +42,35 @@ type PoolReleaseRequest = {
42
42
  pageId: string;
43
43
  };
44
44
 
45
- type JsonRpcId = number;
45
+ type WebSocketCommandId = number;
46
46
 
47
- type JsonRpcError = {
47
+ type WebSocketCommandError = {
48
48
  code?: number;
49
49
  message?: string;
50
50
  };
51
51
 
52
- type JsonRpcMessage = {
53
- error?: JsonRpcError;
54
- id?: JsonRpcId;
52
+ type WebSocketCommandMessage = {
53
+ error?: WebSocketCommandError;
54
+ id?: WebSocketCommandId;
55
+ jsonrpc?: "2.0";
55
56
  method?: string;
56
57
  params?: Record<string, unknown>;
57
58
  result?: Record<string, unknown>;
59
+ sessionId?: string;
60
+ };
61
+
62
+ type CdpCommandFrame = {
63
+ id: WebSocketCommandId;
64
+ method: string;
65
+ params: Record<string, unknown>;
66
+ sessionId?: string;
67
+ };
68
+
69
+ type JsonRpcCommandFrame = {
70
+ id: WebSocketCommandId;
71
+ jsonrpc: "2.0";
72
+ method: string;
73
+ params: Record<string, unknown>;
58
74
  };
59
75
 
60
76
  type CdpFrameTreeNode = {
@@ -641,12 +657,12 @@ function normalizeWebSocketEndpoint(endpoint: string): string {
641
657
  throw new Error(`Unsupported WebSocket endpoint protocol: ${url.protocol}`);
642
658
  }
643
659
 
644
- class JsonRpcWebSocketClient {
660
+ abstract class WebSocketCommandClient {
645
661
  private nextId = 1;
646
662
  private readonly endpoint: string;
647
663
  private readonly listeners = new Map<string, Set<(params: unknown) => void>>();
648
664
  private readonly pending = new Map<
649
- JsonRpcId,
665
+ WebSocketCommandId,
650
666
  {
651
667
  reject: (reason?: unknown) => void;
652
668
  resolve: (value: Record<string, unknown>) => void;
@@ -682,14 +698,7 @@ class JsonRpcWebSocketClient {
682
698
  return await new Promise<Record<string, unknown>>((resolve, reject) => {
683
699
  this.pending.set(id, { resolve, reject });
684
700
 
685
- socket.send(
686
- JSON.stringify({
687
- id,
688
- jsonrpc: "2.0",
689
- method,
690
- params,
691
- }),
692
- );
701
+ socket.send(JSON.stringify(this.createCommandFrame(id, method, params)));
693
702
  });
694
703
  }
695
704
 
@@ -705,6 +714,12 @@ class JsonRpcWebSocketClient {
705
714
  this.socketPromise = undefined;
706
715
  }
707
716
 
717
+ protected abstract createCommandFrame(
718
+ id: WebSocketCommandId,
719
+ method: string,
720
+ params: Record<string, unknown>,
721
+ ): CdpCommandFrame | JsonRpcCommandFrame;
722
+
708
723
  private async getSocket(): Promise<WebSocket> {
709
724
  if (this.socket?.readyState === WebSocket.OPEN) {
710
725
  return this.socket;
@@ -727,7 +742,7 @@ class JsonRpcWebSocketClient {
727
742
  typeof event.data === "string"
728
743
  ? event.data
729
744
  : Buffer.from(event.data as ArrayBufferLike).toString("utf8");
730
- const payload = JSON.parse(rawData) as JsonRpcMessage;
745
+ const payload = JSON.parse(rawData) as WebSocketCommandMessage;
731
746
 
732
747
  if (typeof payload.id === "number") {
733
748
  const pending = this.pending.get(payload.id);
@@ -738,7 +753,11 @@ class JsonRpcWebSocketClient {
738
753
  this.pending.delete(payload.id);
739
754
 
740
755
  if (payload.error) {
741
- pending.reject(new Error(payload.error.message ?? "JSON-RPC command failed"));
756
+ const error = new Error(payload.error.message ?? "JSON-RPC command failed");
757
+ if (typeof payload.error.code === "number") {
758
+ Object.assign(error, { code: payload.error.code });
759
+ }
760
+ pending.reject(error);
742
761
  return;
743
762
  }
744
763
 
@@ -774,6 +793,38 @@ class JsonRpcWebSocketClient {
774
793
  }
775
794
  }
776
795
 
796
+ class CdpWebSocketClient extends WebSocketCommandClient {
797
+ constructor(
798
+ endpoint: string,
799
+ private readonly sessionId?: string,
800
+ ) {
801
+ super(endpoint);
802
+ }
803
+
804
+ protected createCommandFrame(
805
+ id: WebSocketCommandId,
806
+ method: string,
807
+ params: Record<string, unknown>,
808
+ ): CdpCommandFrame {
809
+ return {
810
+ id,
811
+ method,
812
+ params,
813
+ ...(this.sessionId === undefined ? {} : { sessionId: this.sessionId }),
814
+ };
815
+ }
816
+ }
817
+
818
+ class JsonRpcWebSocketClient extends WebSocketCommandClient {
819
+ protected createCommandFrame(
820
+ id: WebSocketCommandId,
821
+ method: string,
822
+ params: Record<string, unknown>,
823
+ ): JsonRpcCommandFrame {
824
+ return { id, jsonrpc: "2.0", method, params };
825
+ }
826
+ }
827
+
777
828
  function flattenCdpFrameTree(
778
829
  node: CdpFrameTreeNode | undefined,
779
830
  out: CdpFrameTreeNode["frame"][] = [],
@@ -974,7 +1025,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
974
1025
  constructor(
975
1026
  readonly pageId: string,
976
1027
  private readonly browserContextId: string | undefined,
977
- private readonly pageClient: JsonRpcWebSocketClient,
1028
+ private readonly pageClient: CdpWebSocketClient,
978
1029
  private readonly release: (request: PoolReleaseRequest) => Promise<void>,
979
1030
  ) {}
980
1031
 
@@ -1335,7 +1386,7 @@ class CdpPoolBrowserClient implements SupportedBrowserClient {
1335
1386
  ...(options?.isolatedContext ? { isolationMode: "browserContext" } : {}),
1336
1387
  }),
1337
1388
  );
1338
- const pageClient = new JsonRpcWebSocketClient(acquireResult.wsEndpoint);
1389
+ const pageClient = new CdpWebSocketClient(acquireResult.wsEndpoint);
1339
1390
  const page = new CdpPoolBrowserPage(
1340
1391
  acquireResult.pageId,
1341
1392
  acquireResult.browserContextId,