@apifuse/provider-sdk 2.2.0-beta.21 → 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 +12 -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.d.ts +2 -0
- package/dist/error-resolution.js +32 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/lint.d.ts +5 -0
- package/dist/lint.js +277 -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 +12 -29
- 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 +36 -0
- package/src/index.ts +25 -0
- package/src/lint.ts +303 -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 +15 -28
- package/src/testing/run.ts +7 -0
- package/src/types.ts +58 -0
package/src/runtime/stt.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/server/serve.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
|
|
|
7
7
|
import {
|
|
8
8
|
SDK_OWNED_PROVIDER_ERROR_CODES,
|
|
9
9
|
SDK_RUNTIME_OWNED_ERROR_CODES,
|
|
10
|
+
SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES,
|
|
10
11
|
} from "../error-resolution.js";
|
|
11
12
|
import {
|
|
12
13
|
AuthError,
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
createNativeNetworkClient,
|
|
48
49
|
} from "../runtime/native-network.js";
|
|
49
50
|
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
51
|
+
import { createOcrClientFromEnv } from "../runtime/ocr.js";
|
|
50
52
|
import {
|
|
51
53
|
PROXY_AUTH_IP_DENIED_CODE,
|
|
52
54
|
PROXY_EDGE_AUTH_REJECTED_CODE,
|
|
@@ -91,6 +93,7 @@ import type {
|
|
|
91
93
|
OperationErrorCode,
|
|
92
94
|
OperationHttpStreamTransport,
|
|
93
95
|
OperationSseTransport,
|
|
96
|
+
OcrContext,
|
|
94
97
|
ProviderErrorStatus,
|
|
95
98
|
ProviderContext,
|
|
96
99
|
ProviderDefinition,
|
|
@@ -388,6 +391,7 @@ function createProviderContext(
|
|
|
388
391
|
: {}),
|
|
389
392
|
trace: createTraceContext(),
|
|
390
393
|
auth: createAuthStub(),
|
|
394
|
+
ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
|
|
391
395
|
stt: options.stt ?? createSttClientFromEnv(provider.stt),
|
|
392
396
|
choice: createProviderChoiceContext({
|
|
393
397
|
providerId: provider.id,
|
|
@@ -511,6 +515,7 @@ function createAuthFlowContext(
|
|
|
511
515
|
]),
|
|
512
516
|
credential,
|
|
513
517
|
context: flowContextStore.context,
|
|
518
|
+
ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
|
|
514
519
|
stt: options.stt ?? createSttClientFromEnv(provider.stt),
|
|
515
520
|
auth: createAuthFlowHelpers({ signal }),
|
|
516
521
|
},
|
|
@@ -595,6 +600,8 @@ export type ProviderServerOptions = {
|
|
|
595
600
|
};
|
|
596
601
|
/** Optional STT override for tests or custom hosts; local/prod normally resolves from env. */
|
|
597
602
|
stt?: SttContext;
|
|
603
|
+
/** Optional OCR override for tests or custom hosts; local/prod normally resolves from env. */
|
|
604
|
+
ocr?: OcrContext;
|
|
598
605
|
/** Optional runtime state override for tests or custom hosts. Production resolves Redis from env and fails closed when unavailable. */
|
|
599
606
|
state?: ProviderRuntimeState;
|
|
600
607
|
/** Allow process-local runtime state only for local development and tests. */
|
|
@@ -948,34 +955,13 @@ function toStatusCode(error: unknown, declaredErrorCode?: OperationErrorCode): P
|
|
|
948
955
|
) {
|
|
949
956
|
return declaredErrorCode.status;
|
|
950
957
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
return 400;
|
|
959
|
-
case "NOT_FOUND":
|
|
960
|
-
case "not_found":
|
|
961
|
-
case "NO_DATA":
|
|
962
|
-
return 404;
|
|
963
|
-
case "RATE_LIMITED":
|
|
964
|
-
case "UPSTREAM_RATE_LIMIT":
|
|
965
|
-
case "LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR":
|
|
966
|
-
return 429;
|
|
967
|
-
// Deterministic upstream business refusal (honest-provider-error-
|
|
968
|
-
// contract): the upstream evaluated the request and said no under
|
|
969
|
-
// its own rules — a conflict with upstream state, never a 5xx.
|
|
970
|
-
case "UPSTREAM_REJECTED":
|
|
971
|
-
return 409;
|
|
972
|
-
case "UPSTREAM_ERROR":
|
|
973
|
-
case "BLOCKED":
|
|
974
|
-
return 502;
|
|
975
|
-
case "STT_UNAVAILABLE":
|
|
976
|
-
case "UNSUPPORTED_STT_BACKEND":
|
|
977
|
-
case "STATEFUL_FORWARDING_REPLAY_CACHE_FULL":
|
|
978
|
-
return 503;
|
|
958
|
+
// Canonical SDK code → status mapping lives in error-resolution.ts so
|
|
959
|
+
// the authoring lint and this runtime path share one source of truth.
|
|
960
|
+
if (typeof error.code === "string") {
|
|
961
|
+
const mappedStatus = SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES.get(error.code);
|
|
962
|
+
if (mappedStatus !== undefined) {
|
|
963
|
+
return mappedStatus;
|
|
964
|
+
}
|
|
979
965
|
}
|
|
980
966
|
if (isTransportError(error)) {
|
|
981
967
|
return error.code === "transport_timeout" ? 504 : 502;
|
|
@@ -2401,6 +2387,7 @@ export async function serve(
|
|
|
2401
2387
|
|
|
2402
2388
|
const app = createServerApp(provider, {
|
|
2403
2389
|
logger: options.logger,
|
|
2390
|
+
ocr: options.ocr,
|
|
2404
2391
|
stt: options.stt,
|
|
2405
2392
|
state: options.state,
|
|
2406
2393
|
allowMemoryStateFallback: options.allowMemoryStateFallback,
|
package/src/testing/run.ts
CHANGED
|
@@ -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,
|
|
@@ -559,6 +560,9 @@ function createUpstreamContext(
|
|
|
559
560
|
: {}),
|
|
560
561
|
trace: { span: async (_name, fn) => fn() },
|
|
561
562
|
auth: { requestField: async (name) => unsupported(`ctx.auth.requestField(${name})`) },
|
|
563
|
+
ocr: createUnsupportedOcrClient(
|
|
564
|
+
"Standard test upstream context does not support ctx.ocr.recognize",
|
|
565
|
+
),
|
|
562
566
|
stt: createUnsupportedSttClient(
|
|
563
567
|
"Standard test upstream context does not support ctx.stt.transcribe",
|
|
564
568
|
),
|
|
@@ -684,6 +688,9 @@ export function createSnapshotContext(rawFixture: unknown): ProviderContext {
|
|
|
684
688
|
auth: {
|
|
685
689
|
requestField: async (name) => unsupported(`ctx.auth.requestField(${name})`),
|
|
686
690
|
},
|
|
691
|
+
ocr: createUnsupportedOcrClient(
|
|
692
|
+
"Standard test snapshot context does not support ctx.ocr.recognize",
|
|
693
|
+
),
|
|
687
694
|
stt: createUnsupportedSttClient(
|
|
688
695
|
"Standard test snapshot context does not support ctx.stt.transcribe",
|
|
689
696
|
),
|
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";
|
|
@@ -1889,6 +1944,7 @@ export interface FlowContext {
|
|
|
1889
1944
|
env: EnvContext;
|
|
1890
1945
|
credential?: CredentialContext;
|
|
1891
1946
|
context: ContextScratchpad;
|
|
1947
|
+
ocr: OcrContext;
|
|
1892
1948
|
stt: SttContext;
|
|
1893
1949
|
auth: AuthFlowTerminalContext;
|
|
1894
1950
|
}
|
|
@@ -2014,6 +2070,7 @@ export interface ProviderContext {
|
|
|
2014
2070
|
browser: BrowserClient;
|
|
2015
2071
|
trace: TraceContext;
|
|
2016
2072
|
auth: AuthContext;
|
|
2073
|
+
ocr: OcrContext;
|
|
2017
2074
|
stt: SttContext;
|
|
2018
2075
|
choice: ProviderChoiceContext;
|
|
2019
2076
|
}
|
|
@@ -2187,6 +2244,7 @@ export interface ProviderDefinition {
|
|
|
2187
2244
|
platform: StealthPlatform;
|
|
2188
2245
|
};
|
|
2189
2246
|
proxy?: ProviderProxyConfig;
|
|
2247
|
+
ocr?: ProviderOcrConfig;
|
|
2190
2248
|
stt?: ProviderSttConfig;
|
|
2191
2249
|
browser?: {
|
|
2192
2250
|
engine: BrowserEngine;
|