@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/CHANGELOG.md +4 -0
- package/bin/apifuse-dev.ts +2 -0
- package/bin/apifuse-record.ts +2 -0
- package/dist/contract-types.d.ts +1 -0
- package/dist/contract.js +2 -0
- package/dist/define.d.ts +2 -1
- package/dist/define.js +15 -0
- package/dist/error-resolution.js +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/runtime/auth-flow.d.ts +2 -1
- package/dist/runtime/auth-flow.js +2 -0
- package/dist/runtime/browser.js +28 -9
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/stt.js +1 -12
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/server/serve.d.ts +3 -1
- package/dist/server/serve.js +4 -0
- package/dist/testing/run.js +3 -0
- package/dist/types.d.ts +51 -0
- package/package.json +1 -1
- package/src/contract-types.ts +1 -0
- package/src/contract.ts +2 -0
- package/src/define.ts +17 -0
- package/src/error-resolution.ts +5 -0
- package/src/index.ts +25 -0
- package/src/runtime/auth-flow.ts +4 -0
- package/src/runtime/browser.ts +70 -19
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/server/serve.ts +7 -0
- package/src/testing/run.ts +7 -0
- package/src/types.ts +58 -0
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,
|
package/src/error-resolution.ts
CHANGED
|
@@ -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,
|
package/src/runtime/auth-flow.ts
CHANGED
|
@@ -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
|
};
|
package/src/runtime/browser.ts
CHANGED
|
@@ -42,19 +42,35 @@ type PoolReleaseRequest = {
|
|
|
42
42
|
pageId: string;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
type
|
|
45
|
+
type WebSocketCommandId = number;
|
|
46
46
|
|
|
47
|
-
type
|
|
47
|
+
type WebSocketCommandError = {
|
|
48
48
|
code?: number;
|
|
49
49
|
message?: string;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
type
|
|
53
|
-
error?:
|
|
54
|
-
id?:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
1389
|
+
const pageClient = new CdpWebSocketClient(acquireResult.wsEndpoint);
|
|
1339
1390
|
const page = new CdpPoolBrowserPage(
|
|
1340
1391
|
acquireResult.pageId,
|
|
1341
1392
|
acquireResult.browserContextId,
|