@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.
@@ -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
+ }
@@ -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
- switch (error.code) {
952
- case "AUTH_REQUIRED":
953
- case "reauth_required":
954
- return 401;
955
- // Unprovisioned declared secret: a deployment/config defect, never an
956
- // upstream failure — explicit 400 (was only reached via fallthrough).
957
- case MISSING_SECRET_CODE:
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,
@@ -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;