@apifuse/provider-sdk 2.2.0-beta.4 → 2.2.0-beta.41
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/AUTHORING.md +501 -13
- package/CHANGELOG.md +169 -1
- package/README.md +72 -14
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +150 -5
- package/bin/apifuse-dev.ts +38 -5
- package/bin/apifuse-migrate-shape.ts +84 -0
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +356 -38
- package/bin/apifuse-perf.ts +14 -13
- package/bin/apifuse-record.ts +691 -68
- package/bin/apifuse-submit-check.ts +2301 -282
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/submit-check-delimited-text.ts +50 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +67 -0
- package/dist/ceremonies/index.d.ts +16 -0
- package/dist/ceremonies/index.js +141 -36
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +16 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +40 -35
- package/dist/cli/migrate-provider-shape.d.ts +52 -0
- package/dist/cli/migrate-provider-shape.js +515 -0
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +4 -4
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +177 -16
- package/dist/config/loader.js +424 -127
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +1 -0
- package/dist/contract.js +3 -0
- package/dist/declaration-validation.d.ts +32 -0
- package/dist/declaration-validation.js +207 -0
- package/dist/define.d.ts +51 -25
- package/dist/define.js +774 -39
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +122 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +40 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +227 -0
- package/dist/health-scenario.d.ts +1842 -0
- package/dist/health-scenario.js +624 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +10 -6
- package/dist/lint.d.ts +6 -1
- package/dist/lint.js +362 -3
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +8 -2
- package/dist/provider.js +3 -1
- package/dist/runtime/auth-flow.d.ts +5 -1
- package/dist/runtime/auth-flow.js +6 -0
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +492 -49
- package/dist/runtime/cache.d.ts +1 -0
- package/dist/runtime/cache.js +169 -15
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +13 -1
- package/dist/runtime/choice.js +490 -102
- package/dist/runtime/executor.d.ts +2 -2
- package/dist/runtime/executor.js +37 -3
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +515 -53
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +366 -8
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-telemetry.d.ts +80 -1
- package/dist/runtime/proxy-telemetry.js +154 -47
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
- package/dist/runtime/resolver-vendors/browser.js +377 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +526 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +407 -0
- package/dist/runtime/resolver-vendors/types.d.ts +94 -0
- package/dist/runtime/resolver-vendors/types.js +96 -0
- package/dist/runtime/resolver.d.ts +60 -0
- package/dist/runtime/resolver.js +737 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +277 -71
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +28 -3
- package/dist/runtime/stealth.js +519 -255
- package/dist/runtime/stt.js +1 -12
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace-config.d.ts +12 -0
- package/dist/runtime/trace-config.js +61 -0
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/index.d.ts +6 -3
- package/dist/server/index.js +3 -3
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test.d.ts +114 -0
- package/dist/server/self-test.js +787 -148
- package/dist/server/serve-implementation.d.ts +225 -0
- package/dist/server/serve-implementation.js +2242 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1130
- package/dist/server/trace-output.d.ts +4 -0
- package/dist/server/trace-output.js +20 -0
- package/dist/server/types.d.ts +30 -5
- package/dist/server/types.js +13 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.js +16 -7
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +489 -21
- package/dist/trace-sanitization.d.ts +5 -0
- package/dist/trace-sanitization.js +45 -0
- package/dist/types.d.ts +563 -33
- package/dist/types.js +1 -0
- package/package.json +44 -5
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/commands.ts +20 -0
- package/src/cli/create.ts +48 -35
- package/src/cli/migrate-provider-shape.ts +701 -0
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +4 -4
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +2 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +665 -163
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +1 -0
- package/src/contract.ts +3 -0
- package/src/declaration-validation.ts +266 -0
- package/src/define.ts +1003 -88
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +127 -0
- package/src/errors.ts +68 -0
- package/src/fixture-sanitization.ts +264 -0
- package/src/health-scenario.ts +875 -0
- package/src/index.ts +205 -8
- package/src/lint.ts +408 -4
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +134 -0
- package/src/runtime/auth-flow.ts +12 -0
- package/src/runtime/browser.ts +661 -63
- package/src/runtime/cache.ts +189 -14
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +631 -120
- package/src/runtime/executor.ts +53 -8
- package/src/runtime/http.ts +641 -61
- package/src/runtime/instrumentation.ts +520 -15
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-telemetry.ts +244 -75
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +680 -1
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +20 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +533 -0
- package/src/runtime/resolver-vendors/capsolver.ts +700 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +539 -0
- package/src/runtime/resolver-vendors/types.ts +212 -0
- package/src/runtime/resolver.ts +1103 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +394 -77
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +675 -289
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +77 -0
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/index.ts +39 -2
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test.ts +1030 -175
- package/src/server/serve-implementation.ts +3338 -0
- package/src/server/serve.ts +1 -1626
- package/src/server/trace-output.ts +32 -0
- package/src/server/types.ts +13 -1
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +17 -7
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +10 -1
- package/src/testing/run.ts +658 -15
- package/src/trace-sanitization.ts +63 -0
- package/src/types.ts +698 -65
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
package/src/define.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import ms from "ms";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
validateFailClosedOperationDeclaration,
|
|
5
|
+
validateFailClosedProviderDeclaration,
|
|
6
|
+
} from "./declaration-validation.js";
|
|
7
|
+
import { SDK_RUNTIME_OWNED_ERROR_CODES } from "./error-resolution.js";
|
|
3
8
|
import { ProviderError, ValidationError } from "./errors.js";
|
|
9
|
+
import { HealthScenarioSchema } from "./health-scenario.js";
|
|
10
|
+
import {
|
|
11
|
+
NativeEgressPolicyValidationError,
|
|
12
|
+
validateNativeProviderConfig,
|
|
13
|
+
} from "./native-egress-policy.js";
|
|
4
14
|
import { safeParseSchemaSync } from "./schema.js";
|
|
15
|
+
import { resolveHealthCheckInputDateTokens } from "./server/self-test-input-tokens.js";
|
|
5
16
|
import type {
|
|
6
17
|
AuthConfig,
|
|
7
18
|
BrowserEngine,
|
|
@@ -14,6 +25,7 @@ import type {
|
|
|
14
25
|
HealthJourneySchedule,
|
|
15
26
|
HealthScheduleRandomization,
|
|
16
27
|
InferSchemaOutput,
|
|
28
|
+
NativeProviderConfig,
|
|
17
29
|
OperationDefinition,
|
|
18
30
|
OperationHandlerResult,
|
|
19
31
|
OperationHttpStreamTransport,
|
|
@@ -21,11 +33,18 @@ import type {
|
|
|
21
33
|
OperationTransport,
|
|
22
34
|
OperationWebSocketTransport,
|
|
23
35
|
ProviderAccessConfig,
|
|
36
|
+
ProviderChallengeKind,
|
|
37
|
+
ProviderContext,
|
|
38
|
+
ProviderContextFor,
|
|
24
39
|
ProviderDefinition,
|
|
25
40
|
ProviderDeploymentOverrides,
|
|
26
41
|
ProviderHealthMonitorConfig,
|
|
42
|
+
ProviderOcrConfig,
|
|
27
43
|
ProviderProxyConfig,
|
|
44
|
+
ProviderProxyProvider,
|
|
28
45
|
ProviderPublicProfile,
|
|
46
|
+
ProviderResolverConfig,
|
|
47
|
+
ProviderResolverVendor,
|
|
29
48
|
ProviderReviewed,
|
|
30
49
|
ProviderSecretDeclaration,
|
|
31
50
|
ProviderStreamEvent,
|
|
@@ -49,6 +68,7 @@ import {
|
|
|
49
68
|
STREAM_IDLE_TIMEOUT_MS_MIN,
|
|
50
69
|
STREAM_MAX_DURATION_MS_MAX,
|
|
51
70
|
STREAM_MAX_DURATION_MS_MIN,
|
|
71
|
+
VALID_OPERATION_ERROR_STATUSES,
|
|
52
72
|
} from "./types.js";
|
|
53
73
|
|
|
54
74
|
type ProviderImplementationSourceAccess =
|
|
@@ -75,18 +95,87 @@ interface ProviderImplementationProfile {
|
|
|
75
95
|
const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
|
|
76
96
|
const OPERATION_ID_REGEX = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
77
97
|
const VALID_RUNTIMES = ["standard", "shared", "browser"] as const;
|
|
78
|
-
const VALID_AUTH_MODES = [
|
|
98
|
+
const VALID_AUTH_MODES = [
|
|
99
|
+
"none",
|
|
100
|
+
"platform-managed",
|
|
101
|
+
"credentials",
|
|
102
|
+
"oauth2",
|
|
103
|
+
"oauth2_proxied",
|
|
104
|
+
] as const;
|
|
105
|
+
const PROXIED_OAUTH_REQUIRED_FIELDS = [
|
|
106
|
+
"authorizeUrl",
|
|
107
|
+
"tokenUrl",
|
|
108
|
+
"customScheme",
|
|
109
|
+
"rewriteProfile",
|
|
110
|
+
"clientIdEnvKey",
|
|
111
|
+
] as const;
|
|
112
|
+
const PROXIED_OAUTH_ALLOWED_FIELDS = new Set([
|
|
113
|
+
...PROXIED_OAUTH_REQUIRED_FIELDS,
|
|
114
|
+
"pkce",
|
|
115
|
+
"authorizeParams",
|
|
116
|
+
"tokenParams",
|
|
117
|
+
]);
|
|
118
|
+
const PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS = new Set([
|
|
119
|
+
"client_id",
|
|
120
|
+
"response_type",
|
|
121
|
+
"state",
|
|
122
|
+
"code_challenge",
|
|
123
|
+
"code_challenge_method",
|
|
124
|
+
]);
|
|
125
|
+
const PROXIED_OAUTH_PROFILE_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
126
|
+
const PROXIED_OAUTH_ENV_KEY_REGEX = /^[A-Z][A-Z0-9_]*__[A-Z0-9_]+$/;
|
|
127
|
+
const CUSTOM_SCHEME_REGEX = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/\S+$/;
|
|
79
128
|
const VALID_PROVIDER_ACCESS_VISIBILITIES = ["public", "early_access"] as const;
|
|
80
129
|
const VALID_PROVIDER_PROXY_MODES = ["disabled", "optional", "required"] as const;
|
|
81
|
-
const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "decodo", "custom"] as const;
|
|
130
|
+
const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "nodemaven", "decodo", "custom"] as const;
|
|
82
131
|
const VALID_PROVIDER_PROXY_AFFINITIES = [
|
|
83
132
|
"request",
|
|
84
133
|
"operation",
|
|
85
134
|
"auth-flow",
|
|
86
135
|
"connection",
|
|
87
136
|
] as const;
|
|
137
|
+
const VALID_PROVIDER_OCR_MODES = ["optional", "required"] as const;
|
|
88
138
|
const VALID_PROVIDER_STT_MODES = ["optional", "required"] as const;
|
|
139
|
+
function exhaustiveLiteralArray<TUnion extends string>() {
|
|
140
|
+
return <const TValues extends readonly TUnion[]>(
|
|
141
|
+
values: TValues,
|
|
142
|
+
..._missing: Exclude<TUnion, TValues[number]> extends never
|
|
143
|
+
? []
|
|
144
|
+
: ["Missing runtime values", Exclude<TUnion, TValues[number]>]
|
|
145
|
+
): TValues => values;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const VALID_PROVIDER_RESOLVER_VENDORS = exhaustiveLiteralArray<ProviderResolverVendor>()([
|
|
149
|
+
"browser",
|
|
150
|
+
"capsolver",
|
|
151
|
+
"capmonster",
|
|
152
|
+
"2captcha",
|
|
153
|
+
"custom",
|
|
154
|
+
] as const);
|
|
155
|
+
export const VALID_PROVIDER_CHALLENGE_KINDS = exhaustiveLiteralArray<ProviderChallengeKind>()([
|
|
156
|
+
"turnstile",
|
|
157
|
+
"recaptcha_v2",
|
|
158
|
+
"recaptcha_v3",
|
|
159
|
+
"hcaptcha",
|
|
160
|
+
"cloudflare_interstitial",
|
|
161
|
+
"aws_waf",
|
|
162
|
+
"akamai_sec_cpt",
|
|
163
|
+
"akamai_sensor",
|
|
164
|
+
] as const);
|
|
89
165
|
const SMARTPROXY_APP_KEY_SECRET = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
|
|
166
|
+
const NODEMAVEN_USERNAME_SECRET = "APIFUSE__PROXY__NODEMAVEN_USERNAME";
|
|
167
|
+
const NODEMAVEN_PASSWORD_SECRET = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
|
|
168
|
+
// Per-vendor provider-declared credential secrets. A required-mode chain must
|
|
169
|
+
// declare every secret of every credentialed vendor it names, so a missing
|
|
170
|
+
// credential fails at build/validation time rather than during a live outage: a
|
|
171
|
+
// declared-but-uncredentialed fallback leg is a silently dead SPOF, which is
|
|
172
|
+
// exactly the failure class the multi-vendor chain exists to remove. Vendors
|
|
173
|
+
// absent from this map (the deprecated `custom`/`decodo` values have no managed
|
|
174
|
+
// adapter) impose no declaration requirement.
|
|
175
|
+
const VENDOR_REQUIRED_SECRETS: Partial<Record<ProviderProxyProvider, readonly string[]>> = {
|
|
176
|
+
smartproxy: [SMARTPROXY_APP_KEY_SECRET],
|
|
177
|
+
nodemaven: [NODEMAVEN_USERNAME_SECRET, NODEMAVEN_PASSWORD_SECRET],
|
|
178
|
+
};
|
|
90
179
|
const RESERVED_OPERATION_IDS = new Set(["auth", "health"]);
|
|
91
180
|
const MCP_TOOL_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_]{0,127}$/;
|
|
92
181
|
const VALID_OPERATION_RISK_CLASSES = ["read", "write", "destructive", "external-send"] as const;
|
|
@@ -114,71 +203,383 @@ function parsePositiveMsDuration(value: string): number | undefined {
|
|
|
114
203
|
return parsed;
|
|
115
204
|
}
|
|
116
205
|
|
|
117
|
-
type ProviderOperation = OperationDefinition<
|
|
118
|
-
type OperationConfig<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
206
|
+
type ProviderOperation = OperationDefinition<any, any, any>;
|
|
207
|
+
type OperationConfig<
|
|
208
|
+
TInput extends SchemaLike,
|
|
209
|
+
TOutput extends SchemaLike,
|
|
210
|
+
TContext = ProviderContext,
|
|
211
|
+
> = Omit<OperationDefinition<TInput, TOutput, TContext>, "handler"> & {
|
|
122
212
|
handler(
|
|
123
|
-
ctx:
|
|
213
|
+
ctx: TContext,
|
|
124
214
|
input: InferSchemaOutput<TInput>,
|
|
125
215
|
):
|
|
126
216
|
| OperationHandlerResult<InferSchemaOutput<TOutput>>
|
|
127
217
|
| Promise<OperationHandlerResult<InferSchemaOutput<TOutput>>>;
|
|
128
218
|
};
|
|
129
|
-
type OperationMapConfig<
|
|
219
|
+
type OperationMapConfig<
|
|
220
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
221
|
+
TContext = ProviderContext,
|
|
222
|
+
> = {
|
|
130
223
|
[K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput>
|
|
131
|
-
? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput>
|
|
224
|
+
? OperationConfig<TInput, TOutput, TContext> | OperationDefinition<TInput, TOutput, TContext>
|
|
132
225
|
: never;
|
|
133
226
|
};
|
|
134
|
-
type StreamOperationConfig<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
227
|
+
type StreamOperationConfig<
|
|
228
|
+
TInput extends SchemaLike,
|
|
229
|
+
TOutput extends SchemaLike,
|
|
230
|
+
TContext = ProviderContext,
|
|
231
|
+
> =
|
|
232
|
+
| SseOperationConfig<TInput, TOutput, TContext>
|
|
233
|
+
| HttpStreamOperationConfig<TInput, TOutput, TContext>
|
|
234
|
+
| WebSocketOperationConfig<TInput, TOutput, TContext>;
|
|
235
|
+
type SseOperationConfig<
|
|
236
|
+
TInput extends SchemaLike,
|
|
237
|
+
TOutput extends SchemaLike,
|
|
238
|
+
TContext = ProviderContext,
|
|
239
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
142
240
|
transport: OperationSseTransport;
|
|
143
241
|
handler(
|
|
144
|
-
ctx:
|
|
242
|
+
ctx: TContext,
|
|
145
243
|
input: InferSchemaOutput<TInput>,
|
|
146
244
|
): AsyncIterable<ProviderStreamEvent> | Promise<AsyncIterable<ProviderStreamEvent>>;
|
|
147
245
|
};
|
|
148
|
-
type HttpStreamOperationConfig<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
246
|
+
type HttpStreamOperationConfig<
|
|
247
|
+
TInput extends SchemaLike,
|
|
248
|
+
TOutput extends SchemaLike,
|
|
249
|
+
TContext = ProviderContext,
|
|
250
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
152
251
|
transport: OperationHttpStreamTransport;
|
|
153
252
|
handler(
|
|
154
|
-
ctx:
|
|
253
|
+
ctx: TContext,
|
|
155
254
|
input: InferSchemaOutput<TInput>,
|
|
156
255
|
): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
157
256
|
};
|
|
158
|
-
type WebSocketOperationConfig<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
257
|
+
type WebSocketOperationConfig<
|
|
258
|
+
TInput extends SchemaLike,
|
|
259
|
+
TOutput extends SchemaLike,
|
|
260
|
+
TContext = ProviderContext,
|
|
261
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
162
262
|
transport: OperationWebSocketTransport;
|
|
163
263
|
handler(
|
|
164
|
-
ctx:
|
|
264
|
+
ctx: TContext,
|
|
165
265
|
input: InferSchemaOutput<TInput>,
|
|
166
266
|
): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
167
267
|
};
|
|
168
268
|
|
|
169
|
-
type
|
|
269
|
+
type AuthStartHandlerNoInputGuard<TStart> = TStart extends (...args: infer TArgs) => unknown
|
|
270
|
+
? TArgs["length"] extends 0 | 1
|
|
271
|
+
? unknown
|
|
272
|
+
: {
|
|
273
|
+
"auth start handlers must not declare input parameters; return a form turn from start and receive user input in continue": never;
|
|
274
|
+
}
|
|
275
|
+
: unknown;
|
|
276
|
+
|
|
277
|
+
export type AuthStartNoInputGuard<TConfig> = TConfig extends {
|
|
170
278
|
auth?: { flow?: { start: infer TStart } };
|
|
171
279
|
}
|
|
172
|
-
? TStart
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
280
|
+
? AuthStartHandlerNoInputGuard<TStart>
|
|
281
|
+
: TConfig extends { start: infer TStart }
|
|
282
|
+
? AuthStartHandlerNoInputGuard<TStart>
|
|
283
|
+
: unknown;
|
|
284
|
+
|
|
285
|
+
function splitAuthStartParameters(parameters: string): string[] | undefined {
|
|
286
|
+
const parts: string[] = [];
|
|
287
|
+
let start = 0;
|
|
288
|
+
let round = 0;
|
|
289
|
+
let square = 0;
|
|
290
|
+
let curly = 0;
|
|
291
|
+
let quote: "'" | '"' | "`" | undefined;
|
|
292
|
+
let escaped = false;
|
|
293
|
+
let lineComment = false;
|
|
294
|
+
let blockComment = false;
|
|
295
|
+
const templateDepths = [0];
|
|
296
|
+
templateDepths.length = 0;
|
|
297
|
+
|
|
298
|
+
for (let index = 0; index < parameters.length; index++) {
|
|
299
|
+
const character = parameters[index];
|
|
300
|
+
const nextCharacter = parameters[index + 1];
|
|
301
|
+
if (lineComment) {
|
|
302
|
+
if (character === "\n" || character === "\r") lineComment = false;
|
|
303
|
+
else continue;
|
|
304
|
+
}
|
|
305
|
+
if (blockComment) {
|
|
306
|
+
if (character === "*" && nextCharacter === "/") {
|
|
307
|
+
blockComment = false;
|
|
308
|
+
index++;
|
|
309
|
+
}
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (quote) {
|
|
313
|
+
if (escaped) {
|
|
314
|
+
escaped = false;
|
|
315
|
+
} else if (character === "\\") {
|
|
316
|
+
escaped = true;
|
|
317
|
+
} else if (quote === "`" && character === "$" && nextCharacter === "{") {
|
|
318
|
+
curly++;
|
|
319
|
+
templateDepths.push(curly);
|
|
320
|
+
quote = undefined;
|
|
321
|
+
index++;
|
|
322
|
+
} else if (character === quote) {
|
|
323
|
+
quote = undefined;
|
|
324
|
+
}
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (character === "'" || character === '"' || character === "`") {
|
|
328
|
+
quote = character;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (character === "/" && nextCharacter === "/") {
|
|
332
|
+
lineComment = true;
|
|
333
|
+
index++;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (character === "/" && nextCharacter === "*") {
|
|
337
|
+
blockComment = true;
|
|
338
|
+
index++;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (character === "/") return undefined;
|
|
342
|
+
// Annex B HTML-like comments are not lexed here; give up rather than
|
|
343
|
+
// risk misreading the parameter list.
|
|
344
|
+
if (character === "<" && parameters.startsWith("!--", index + 1)) return undefined;
|
|
345
|
+
if (character === "-" && parameters.startsWith("->", index + 1)) return undefined;
|
|
346
|
+
if (character === "(") round++;
|
|
347
|
+
else if (character === ")") round--;
|
|
348
|
+
else if (character === "[") square++;
|
|
349
|
+
else if (character === "]") square--;
|
|
350
|
+
else if (character === "{") curly++;
|
|
351
|
+
else if (character === "}") {
|
|
352
|
+
if (templateDepths.at(-1) === curly) {
|
|
353
|
+
templateDepths.pop();
|
|
354
|
+
curly--;
|
|
355
|
+
quote = "`";
|
|
356
|
+
} else curly--;
|
|
357
|
+
} else if (character === "," && round === 0 && square === 0 && curly === 0) {
|
|
358
|
+
parts.push(parameters.slice(start, index));
|
|
359
|
+
start = index + 1;
|
|
360
|
+
}
|
|
361
|
+
if (round < 0 || square < 0 || curly < 0) return undefined;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (
|
|
365
|
+
quote ||
|
|
366
|
+
blockComment ||
|
|
367
|
+
templateDepths.length > 0 ||
|
|
368
|
+
round !== 0 ||
|
|
369
|
+
square !== 0 ||
|
|
370
|
+
curly !== 0
|
|
371
|
+
)
|
|
372
|
+
return undefined;
|
|
373
|
+
parts.push(parameters.slice(start));
|
|
374
|
+
return parts;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function authStartParameterList(source: string): string | undefined {
|
|
378
|
+
let index = 0;
|
|
379
|
+
while (index < source.length && /\s/.test(source[index] ?? "")) index++;
|
|
380
|
+
if (index >= source.length) return undefined;
|
|
381
|
+
|
|
382
|
+
let openIndex = -1;
|
|
383
|
+
let parenthesizedArrow = false;
|
|
384
|
+
let asyncMethodOrArrow = false;
|
|
385
|
+
const skipTrivia = () => {
|
|
386
|
+
while (index < source.length) {
|
|
387
|
+
if (/\s/.test(source[index] ?? "")) {
|
|
388
|
+
index++;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (source[index] === "/" && source[index + 1] === "/") {
|
|
392
|
+
index += 2;
|
|
393
|
+
while (index < source.length && source[index] !== "\n" && source[index] !== "\r") index++;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (source[index] === "/" && source[index + 1] === "*") {
|
|
397
|
+
const end = source.indexOf("*/", index + 2);
|
|
398
|
+
if (end < 0) {
|
|
399
|
+
index = source.length;
|
|
400
|
+
return;
|
|
177
401
|
}
|
|
178
|
-
|
|
179
|
-
|
|
402
|
+
index = end + 2;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
180
408
|
|
|
181
|
-
|
|
409
|
+
const initial = source.slice(index);
|
|
410
|
+
const isAsync = initial.startsWith("async") && !/[\w$]/.test(initial[5] ?? "");
|
|
411
|
+
if (isAsync) {
|
|
412
|
+
index += 5;
|
|
413
|
+
skipTrivia();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const afterAsync = source.slice(index);
|
|
417
|
+
const isFunction = afterAsync.startsWith("function") && !/[\w$]/.test(afterAsync[8] ?? "");
|
|
418
|
+
if (isFunction) {
|
|
419
|
+
index += 8;
|
|
420
|
+
skipTrivia();
|
|
421
|
+
if (source[index] === "*") {
|
|
422
|
+
index++;
|
|
423
|
+
skipTrivia();
|
|
424
|
+
}
|
|
425
|
+
} else if (source[index] === "*") {
|
|
426
|
+
index++;
|
|
427
|
+
skipTrivia();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (source[index] === "(") {
|
|
431
|
+
openIndex = index;
|
|
432
|
+
parenthesizedArrow = !isFunction;
|
|
433
|
+
asyncMethodOrArrow = isAsync && !isFunction;
|
|
434
|
+
} else if (isFunction) {
|
|
435
|
+
if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
|
|
436
|
+
index++;
|
|
437
|
+
while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
|
|
438
|
+
skipTrivia();
|
|
439
|
+
if (source[index] !== "(") return undefined;
|
|
440
|
+
openIndex = index;
|
|
441
|
+
} else {
|
|
442
|
+
const identifierStart = index;
|
|
443
|
+
if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
|
|
444
|
+
index++;
|
|
445
|
+
while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
|
|
446
|
+
const firstIdentifier = source.slice(identifierStart, index);
|
|
447
|
+
skipTrivia();
|
|
448
|
+
if (source[index] === "=" && source[index + 1] === ">") return undefined;
|
|
449
|
+
if (source[index] !== "(") {
|
|
450
|
+
if (firstIdentifier !== "get" && firstIdentifier !== "set") return undefined;
|
|
451
|
+
if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
|
|
452
|
+
index++;
|
|
453
|
+
while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
|
|
454
|
+
skipTrivia();
|
|
455
|
+
}
|
|
456
|
+
if (source[index] !== "(") return undefined;
|
|
457
|
+
openIndex = index;
|
|
458
|
+
}
|
|
459
|
+
if (openIndex < 0) return undefined;
|
|
460
|
+
|
|
461
|
+
let depth = 1;
|
|
462
|
+
let square = 0;
|
|
463
|
+
let curly = 0;
|
|
464
|
+
let quote: "'" | '"' | "`" | undefined;
|
|
465
|
+
let escaped = false;
|
|
466
|
+
let lineComment = false;
|
|
467
|
+
let blockComment = false;
|
|
468
|
+
const templateDepths = [0];
|
|
469
|
+
templateDepths.length = 0;
|
|
470
|
+
for (index = openIndex + 1; index < source.length; index++) {
|
|
471
|
+
const character = source[index];
|
|
472
|
+
const nextCharacter = source[index + 1];
|
|
473
|
+
if (lineComment) {
|
|
474
|
+
if (character === "\n" || character === "\r") lineComment = false;
|
|
475
|
+
else continue;
|
|
476
|
+
}
|
|
477
|
+
if (blockComment) {
|
|
478
|
+
if (character === "*" && nextCharacter === "/") {
|
|
479
|
+
blockComment = false;
|
|
480
|
+
index++;
|
|
481
|
+
}
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (quote) {
|
|
485
|
+
if (escaped) escaped = false;
|
|
486
|
+
else if (character === "\\") escaped = true;
|
|
487
|
+
else if (quote === "`" && character === "$" && nextCharacter === "{") {
|
|
488
|
+
curly++;
|
|
489
|
+
templateDepths.push(curly);
|
|
490
|
+
quote = undefined;
|
|
491
|
+
index++;
|
|
492
|
+
} else if (character === quote) quote = undefined;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (character === "'" || character === '"' || character === "`") {
|
|
496
|
+
quote = character;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (character === "/" && nextCharacter === "/") {
|
|
500
|
+
lineComment = true;
|
|
501
|
+
index++;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (character === "/" && nextCharacter === "*") {
|
|
505
|
+
blockComment = true;
|
|
506
|
+
index++;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (character === "/") return undefined;
|
|
510
|
+
// Annex B HTML-like comments are not lexed here; give up rather than
|
|
511
|
+
// risk misreading the parameter list.
|
|
512
|
+
if (character === "<" && source.startsWith("!--", index + 1)) return undefined;
|
|
513
|
+
if (character === "-" && source.startsWith("->", index + 1)) return undefined;
|
|
514
|
+
if (character === "(") depth++;
|
|
515
|
+
else if (character === ")") {
|
|
516
|
+
depth--;
|
|
517
|
+
if (depth === 0 && square === 0 && curly === 0) {
|
|
518
|
+
const closeIndex = index;
|
|
519
|
+
if (parenthesizedArrow) {
|
|
520
|
+
index++;
|
|
521
|
+
skipTrivia();
|
|
522
|
+
const hasArrow = source[index] === "=" && source[index + 1] === ">";
|
|
523
|
+
if (!hasArrow && (!asyncMethodOrArrow || source[index] !== "{")) return undefined;
|
|
524
|
+
}
|
|
525
|
+
return source.slice(openIndex + 1, closeIndex);
|
|
526
|
+
}
|
|
527
|
+
if (depth < 0) return undefined;
|
|
528
|
+
} else if (character === "[") square++;
|
|
529
|
+
else if (character === "]") {
|
|
530
|
+
square--;
|
|
531
|
+
if (square < 0) return undefined;
|
|
532
|
+
} else if (character === "{") curly++;
|
|
533
|
+
else if (character === "}") {
|
|
534
|
+
if (templateDepths.at(-1) === curly) {
|
|
535
|
+
templateDepths.pop();
|
|
536
|
+
curly--;
|
|
537
|
+
quote = "`";
|
|
538
|
+
} else curly--;
|
|
539
|
+
if (curly < 0) return undefined;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Conservative defense in depth for defaulted second parameters, which
|
|
547
|
+
* JavaScript intentionally omits from Function.length. Ambiguous source is
|
|
548
|
+
* ignored so this check can never reject a valid provider on weak evidence.
|
|
549
|
+
*/
|
|
550
|
+
function authStartHasHiddenInput(start: unknown): boolean {
|
|
551
|
+
let source: string;
|
|
552
|
+
try {
|
|
553
|
+
source = Function.prototype.toString.call(start);
|
|
554
|
+
} catch {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
if (
|
|
558
|
+
!source ||
|
|
559
|
+
source.includes("[native code]") ||
|
|
560
|
+
/^\s*(?:async\s+)?function\s+bound\b/.test(source)
|
|
561
|
+
)
|
|
562
|
+
return false;
|
|
563
|
+
|
|
564
|
+
const parameters = authStartParameterList(source);
|
|
565
|
+
if (!parameters) return false;
|
|
566
|
+
const parts = splitAuthStartParameters(parameters);
|
|
567
|
+
if (!parts || parts.length < 2) return false;
|
|
568
|
+
|
|
569
|
+
// Require ordinary, readable source formatting. This intentionally fails
|
|
570
|
+
// open for minified output and for transpilers that rewrite defaults.
|
|
571
|
+
const commaIndex = parameters.indexOf(",");
|
|
572
|
+
if (commaIndex < 0 || !/\s/.test(parameters[commaIndex + 1] ?? "")) return false;
|
|
573
|
+
const first = parts[0].trim();
|
|
574
|
+
const second = parts[1].trim();
|
|
575
|
+
const identifier = /^[_$A-Za-z][_$A-Za-z0-9]*/;
|
|
576
|
+
const firstName = first.match(identifier)?.[0];
|
|
577
|
+
const secondName = second.match(identifier)?.[0];
|
|
578
|
+
if (!firstName || !secondName || firstName.length < 3 || secondName.length < 3) return false;
|
|
579
|
+
return /\s=\s/.test(second);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export interface ProviderDeclaration {
|
|
182
583
|
id: string;
|
|
183
584
|
version: string;
|
|
184
585
|
runtime: "standard" | "shared" | "browser";
|
|
@@ -189,20 +590,35 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
189
590
|
* resolves omitted fields against the runtime deployment profiles.
|
|
190
591
|
*/
|
|
191
592
|
deployment?: ProviderDeploymentOverrides;
|
|
593
|
+
/** Declares that provider operations use the SDK HTTP client. */
|
|
594
|
+
http?: true;
|
|
192
595
|
allowedHosts?: string[];
|
|
596
|
+
native?: NativeProviderConfig;
|
|
193
597
|
stealth?: {
|
|
194
598
|
profile: string;
|
|
195
599
|
platform: StealthPlatform;
|
|
196
600
|
};
|
|
197
601
|
proxy?: ProviderProxyConfig;
|
|
602
|
+
ocr?: ProviderOcrConfig;
|
|
198
603
|
stt?: ProviderSttConfig;
|
|
604
|
+
resolver?: ProviderResolverConfig;
|
|
199
605
|
browser?: { engine: BrowserEngine };
|
|
200
606
|
auth?: AuthConfig;
|
|
607
|
+
/** Declares that provider operations issue and consume SDK choice tokens. */
|
|
608
|
+
choice?: true;
|
|
201
609
|
reviewed?: ProviderReviewed;
|
|
202
610
|
access?: ProviderAccessConfig;
|
|
203
611
|
secrets?: ProviderSecretDeclaration[];
|
|
612
|
+
/** Declares that provider operations read SDK-managed environment values. */
|
|
613
|
+
env?: true;
|
|
204
614
|
credential?: CredentialDeclaration;
|
|
205
615
|
context?: ContextDeclaration;
|
|
616
|
+
/** Declares that provider operations use SDK-managed persistent state. */
|
|
617
|
+
state?: true;
|
|
618
|
+
/** Declares that provider operations use the SDK provider cache. */
|
|
619
|
+
cache?: true;
|
|
620
|
+
/** Declares that provider operations access runtime-resolvable files. */
|
|
621
|
+
files?: true;
|
|
206
622
|
meta: {
|
|
207
623
|
displayName: string;
|
|
208
624
|
displayNameKey?: string;
|
|
@@ -224,25 +640,35 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
224
640
|
publicSchemaFieldNames?: "normalized";
|
|
225
641
|
};
|
|
226
642
|
};
|
|
227
|
-
operations: OperationMapConfig<TOperations>;
|
|
228
643
|
healthMonitor?: ProviderHealthMonitorConfig;
|
|
229
644
|
/** New name for `healthMonitor` (transitional alias); declaring both is a ValidationError. */
|
|
230
645
|
healthProbe?: ProviderHealthMonitorConfig;
|
|
231
646
|
healthJourneys?: readonly HealthJourneyDefinition[];
|
|
232
647
|
}
|
|
233
648
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
649
|
+
interface ProviderConfig<
|
|
650
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
651
|
+
TContext = ProviderContext,
|
|
652
|
+
> extends ProviderDeclaration {
|
|
653
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
239
654
|
}
|
|
240
655
|
|
|
241
|
-
/** Define
|
|
242
|
-
export function
|
|
243
|
-
operation
|
|
244
|
-
|
|
245
|
-
|
|
656
|
+
/** Define one factored provider operation with schema-driven handler inference. */
|
|
657
|
+
export function defineOperation<TContext>() {
|
|
658
|
+
return function operation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
659
|
+
config: OperationConfig<TInput, TOutput, TContext>,
|
|
660
|
+
): OperationDefinition<TInput, TOutput, TContext> {
|
|
661
|
+
return config;
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/** Define a factored non-JSON operation with explicit transport metadata. */
|
|
666
|
+
export function defineStreamOperation<TContext>() {
|
|
667
|
+
return function streamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
668
|
+
config: StreamOperationConfig<TInput, TOutput, TContext>,
|
|
669
|
+
): OperationDefinition<TInput, TOutput, TContext> {
|
|
670
|
+
return config;
|
|
671
|
+
};
|
|
246
672
|
}
|
|
247
673
|
|
|
248
674
|
function assertObjectConfig(value: unknown): asserts value is Record<string, unknown> {
|
|
@@ -279,18 +705,133 @@ function assertLiteralField<TValue extends string>(
|
|
|
279
705
|
);
|
|
280
706
|
}
|
|
281
707
|
}
|
|
282
|
-
|
|
708
|
+
|
|
709
|
+
function validateProxiedOAuthParams(
|
|
710
|
+
value: unknown,
|
|
711
|
+
field: "authorizeParams" | "tokenParams",
|
|
712
|
+
providerId: string,
|
|
713
|
+
): void {
|
|
714
|
+
if (value === undefined) return;
|
|
715
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
716
|
+
throw new ValidationError(
|
|
717
|
+
`Provider "${providerId}" auth.proxied.${field} must be an object of string values.`,
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
for (const [key, paramValue] of Object.entries(value)) {
|
|
721
|
+
if (!key.trim() || typeof paramValue !== "string") {
|
|
722
|
+
throw new ValidationError(
|
|
723
|
+
`Provider "${providerId}" auth.proxied.${field} must contain non-empty keys and string values.`,
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
if (
|
|
727
|
+
field === "authorizeParams" &&
|
|
728
|
+
PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())
|
|
729
|
+
) {
|
|
730
|
+
throw new ValidationError(
|
|
731
|
+
`Provider "${providerId}" auth.proxied.authorizeParams cannot override reserved parameter "${key}".`,
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function validateProxiedOAuthAuth(auth: Record<string, unknown>, providerId: string): void {
|
|
738
|
+
const proxied = auth.proxied;
|
|
739
|
+
if (auth.mode !== "oauth2_proxied") {
|
|
740
|
+
if (proxied !== undefined) {
|
|
741
|
+
throw new ValidationError(
|
|
742
|
+
`Provider "${providerId}" auth.proxied is only valid when auth.mode is "oauth2_proxied".`,
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (!proxied || typeof proxied !== "object" || Array.isArray(proxied)) {
|
|
748
|
+
throw new ValidationError(
|
|
749
|
+
`Provider "${providerId}" with auth.mode "oauth2_proxied" must declare auth.proxied.`,
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
const config = Object.fromEntries(Object.entries(proxied));
|
|
753
|
+
for (const key of Object.keys(config)) {
|
|
754
|
+
if (!PROXIED_OAUTH_ALLOWED_FIELDS.has(key)) {
|
|
755
|
+
throw new ValidationError(
|
|
756
|
+
`Provider "${providerId}" has unknown auth.proxied field "${key}".`,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
for (const field of PROXIED_OAUTH_REQUIRED_FIELDS) {
|
|
761
|
+
if (typeof config[field] !== "string" || !config[field].trim()) {
|
|
762
|
+
throw new ValidationError(
|
|
763
|
+
`Provider "${providerId}" auth.proxied.${field} must be a non-empty string.`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
for (const field of ["authorizeUrl", "tokenUrl"] as const) {
|
|
768
|
+
try {
|
|
769
|
+
const endpoint = new URL(String(config[field]));
|
|
770
|
+
if (
|
|
771
|
+
endpoint.protocol !== "https:" ||
|
|
772
|
+
endpoint.username ||
|
|
773
|
+
endpoint.password ||
|
|
774
|
+
endpoint.hash
|
|
775
|
+
) {
|
|
776
|
+
throw new Error("invalid endpoint");
|
|
777
|
+
}
|
|
778
|
+
} catch {
|
|
779
|
+
throw new ValidationError(
|
|
780
|
+
`Provider "${providerId}" auth.proxied.${field} must be an absolute HTTPS URL without credentials or a fragment.`,
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const customScheme = String(config.customScheme);
|
|
785
|
+
if (
|
|
786
|
+
!CUSTOM_SCHEME_REGEX.test(customScheme) ||
|
|
787
|
+
customScheme.toLowerCase().startsWith("http://") ||
|
|
788
|
+
customScheme.toLowerCase().startsWith("https://")
|
|
789
|
+
) {
|
|
790
|
+
throw new ValidationError(
|
|
791
|
+
`Provider "${providerId}" auth.proxied.customScheme must be a non-HTTP custom-scheme URL prefix.`,
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
if (!PROXIED_OAUTH_PROFILE_REGEX.test(String(config.rewriteProfile))) {
|
|
795
|
+
throw new ValidationError(
|
|
796
|
+
`Provider "${providerId}" auth.proxied.rewriteProfile must be a kebab-case profile name.`,
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
if (!PROXIED_OAUTH_ENV_KEY_REGEX.test(String(config.clientIdEnvKey))) {
|
|
800
|
+
throw new ValidationError(
|
|
801
|
+
`Provider "${providerId}" auth.proxied.clientIdEnvKey must be an APIFuse-style uppercase environment key.`,
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
if (config.pkce !== undefined && config.pkce !== "S256" && config.pkce !== "none") {
|
|
805
|
+
throw new ValidationError(
|
|
806
|
+
`Provider "${providerId}" auth.proxied.pkce must be "S256" or "none".`,
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
validateProxiedOAuthParams(config.authorizeParams, "authorizeParams", providerId);
|
|
810
|
+
validateProxiedOAuthParams(config.tokenParams, "tokenParams", providerId);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function validateProviderDeclarationShape(config: unknown): void {
|
|
283
814
|
assertObjectConfig(config);
|
|
284
815
|
assertRequiredField(config, "id");
|
|
285
816
|
assertRequiredField(config, "version", String(config.id));
|
|
286
817
|
assertRequiredField(config, "runtime", String(config.id));
|
|
287
818
|
assertRequiredField(config, "meta", String(config.id));
|
|
288
|
-
assertRequiredField(config, "operations", String(config.id));
|
|
289
819
|
if (typeof config.runtime === "string")
|
|
290
820
|
assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
|
|
821
|
+
if (config.native !== undefined && config.runtime === "browser") {
|
|
822
|
+
throw new ValidationError(
|
|
823
|
+
`Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`,
|
|
824
|
+
{
|
|
825
|
+
fix: 'Use runtime: "standard" or runtime: "shared", or remove the native declaration.',
|
|
826
|
+
},
|
|
827
|
+
);
|
|
828
|
+
}
|
|
291
829
|
const auth = config.auth;
|
|
292
830
|
if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
|
|
293
831
|
assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
|
|
832
|
+
if (auth && typeof auth === "object" && !Array.isArray(auth)) {
|
|
833
|
+
validateProxiedOAuthAuth(Object.fromEntries(Object.entries(auth)), String(config.id));
|
|
834
|
+
}
|
|
294
835
|
if (auth && typeof auth === "object" && "exchange" in auth) {
|
|
295
836
|
throw new ProviderError(
|
|
296
837
|
`Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`,
|
|
@@ -316,6 +857,24 @@ function validateProviderShape(config: unknown): void {
|
|
|
316
857
|
},
|
|
317
858
|
);
|
|
318
859
|
}
|
|
860
|
+
if (
|
|
861
|
+
auth &&
|
|
862
|
+
typeof auth === "object" &&
|
|
863
|
+
"flow" in auth &&
|
|
864
|
+
auth.flow &&
|
|
865
|
+
typeof auth.flow === "object" &&
|
|
866
|
+
"start" in auth.flow &&
|
|
867
|
+
typeof auth.flow.start === "function" &&
|
|
868
|
+
auth.flow.start.length <= 1 &&
|
|
869
|
+
authStartHasHiddenInput(auth.flow.start)
|
|
870
|
+
) {
|
|
871
|
+
throw new ProviderError(
|
|
872
|
+
`Provider "${String(config.id)}" auth.flow.start must not declare an input parameter`,
|
|
873
|
+
{
|
|
874
|
+
fix: "Return a form turn from start(ctx), then receive user input in continue(ctx, input).",
|
|
875
|
+
},
|
|
876
|
+
);
|
|
877
|
+
}
|
|
319
878
|
const access = config.access;
|
|
320
879
|
if (access !== undefined) {
|
|
321
880
|
if (!access || typeof access !== "object" || Array.isArray(access)) {
|
|
@@ -354,6 +913,11 @@ function validateProviderShape(config: unknown): void {
|
|
|
354
913
|
}
|
|
355
914
|
}
|
|
356
915
|
|
|
916
|
+
function validateProviderImplementationShape(config: { id: string }): void {
|
|
917
|
+
const configRecord = config as unknown as Record<string, unknown>;
|
|
918
|
+
assertRequiredField(configRecord, "operations", String(config.id));
|
|
919
|
+
}
|
|
920
|
+
|
|
357
921
|
function validateProviderProxy(config: {
|
|
358
922
|
id: string;
|
|
359
923
|
proxy?: ProviderProxyConfig;
|
|
@@ -371,11 +935,24 @@ function validateProviderProxy(config: {
|
|
|
371
935
|
},
|
|
372
936
|
);
|
|
373
937
|
}
|
|
374
|
-
rejectUnknownFields(proxy, new Set(["mode", "provider", "geo", "session"]), "proxy");
|
|
938
|
+
rejectUnknownFields(proxy, new Set(["mode", "provider", "providers", "geo", "session"]), "proxy");
|
|
375
939
|
assertLiteralField(proxy.mode, "proxy.mode", VALID_PROVIDER_PROXY_MODES, config.id);
|
|
376
940
|
if (proxy.provider !== undefined) {
|
|
377
941
|
assertLiteralField(proxy.provider, "proxy.provider", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
|
|
378
942
|
}
|
|
943
|
+
if (proxy.providers !== undefined) {
|
|
944
|
+
if (!Array.isArray(proxy.providers) || proxy.providers.length === 0) {
|
|
945
|
+
throw new ValidationError(
|
|
946
|
+
`Provider "${config.id}" has invalid proxy.providers: must be a non-empty array of proxy vendors.`,
|
|
947
|
+
{
|
|
948
|
+
fix: `Use proxy.providers: ["smartproxy", "nodemaven"] to declare an ordered fallback chain.`,
|
|
949
|
+
},
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
for (const vendor of proxy.providers) {
|
|
953
|
+
assertLiteralField(vendor, "proxy.providers[]", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
379
956
|
if (proxy.geo !== undefined) {
|
|
380
957
|
if (!proxy.geo || typeof proxy.geo !== "object" || Array.isArray(proxy.geo)) {
|
|
381
958
|
throw new ValidationError(
|
|
@@ -409,7 +986,7 @@ function validateProviderProxy(config: {
|
|
|
409
986
|
}
|
|
410
987
|
rejectUnknownFields(
|
|
411
988
|
proxy.session,
|
|
412
|
-
new Set(["affinity", "lifetimeMinutes", "poolSize"]),
|
|
989
|
+
new Set(["affinity", "lifetimeMinutes", "poolSize", "drainLeadSeconds"]),
|
|
413
990
|
"proxy.session",
|
|
414
991
|
);
|
|
415
992
|
if (proxy.session.affinity !== undefined) {
|
|
@@ -432,20 +1009,95 @@ function validateProviderProxy(config: {
|
|
|
432
1009
|
`Provider "${config.id}" has invalid proxy.session.poolSize: must be a positive integer.`,
|
|
433
1010
|
);
|
|
434
1011
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
(
|
|
439
|
-
)
|
|
440
|
-
|
|
1012
|
+
const drainLeadSeconds = proxy.session.drainLeadSeconds;
|
|
1013
|
+
if (
|
|
1014
|
+
drainLeadSeconds !== undefined &&
|
|
1015
|
+
(!Number.isFinite(drainLeadSeconds) || drainLeadSeconds <= 0)
|
|
1016
|
+
) {
|
|
1017
|
+
throw new ValidationError(
|
|
1018
|
+
`Provider "${config.id}" has invalid proxy.session.drainLeadSeconds: must be a positive number of seconds.`,
|
|
1019
|
+
{
|
|
1020
|
+
fix: `Use proxy.session.drainLeadSeconds: 120 to receive the sticky-expiry drain event 120s before hard expiry.`,
|
|
1021
|
+
},
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
// A drain lead longer than the sticky lifetime would fire the expiring
|
|
1025
|
+
// event before the session is even established, so the provider would
|
|
1026
|
+
// never get a usable window. Reject the contradiction at build time.
|
|
1027
|
+
if (
|
|
1028
|
+
drainLeadSeconds !== undefined &&
|
|
1029
|
+
lifetime !== undefined &&
|
|
1030
|
+
Number.isFinite(lifetime) &&
|
|
1031
|
+
drainLeadSeconds >= lifetime * 60
|
|
1032
|
+
) {
|
|
441
1033
|
throw new ValidationError(
|
|
442
|
-
`Provider "${config.id}"
|
|
1034
|
+
`Provider "${config.id}" has proxy.session.drainLeadSeconds (${drainLeadSeconds}s) greater than or equal to proxy.session.lifetimeMinutes (${lifetime}m).`,
|
|
443
1035
|
{
|
|
444
|
-
fix: `
|
|
1036
|
+
fix: `Lower drainLeadSeconds below the sticky lifetime so the drain event leaves a usable session window.`,
|
|
445
1037
|
},
|
|
446
1038
|
);
|
|
447
1039
|
}
|
|
448
1040
|
}
|
|
1041
|
+
// Every credentialed vendor in a required-mode chain must declare its
|
|
1042
|
+
// provider secret(s) so a missing credential fails at build/validation time,
|
|
1043
|
+
// not during a live outage. This covers the fallback legs too (not just the
|
|
1044
|
+
// first vendor): a declared-but-uncredentialed nodemaven fallback would leave
|
|
1045
|
+
// the chain silently down to a single vendor, reintroducing the SPOF the chain
|
|
1046
|
+
// removes.
|
|
1047
|
+
const vendorChain =
|
|
1048
|
+
proxy.providers && proxy.providers.length > 0
|
|
1049
|
+
? proxy.providers
|
|
1050
|
+
: proxy.provider
|
|
1051
|
+
? [proxy.provider]
|
|
1052
|
+
: [];
|
|
1053
|
+
if (proxy.mode === "required") {
|
|
1054
|
+
for (const vendor of vendorChain) {
|
|
1055
|
+
const requiredSecrets = VENDOR_REQUIRED_SECRETS[vendor];
|
|
1056
|
+
if (!requiredSecrets) continue;
|
|
1057
|
+
for (const secretName of requiredSecrets) {
|
|
1058
|
+
// Match the canonical runtime gate (assertRequiredSecretsPresent /
|
|
1059
|
+
// listMissingRequiredSecrets), which enforces only `required === true`
|
|
1060
|
+
// declarations. A declaration that omits `required` (defaulting to
|
|
1061
|
+
// optional) is skipped at runtime, so accepting it here would pass
|
|
1062
|
+
// validation while leaving the credential unenforced until proxy
|
|
1063
|
+
// resolution during a live request — the fail-open gap this check exists
|
|
1064
|
+
// to close.
|
|
1065
|
+
const declared = config.secrets?.some(
|
|
1066
|
+
(secret) => secret.name === secretName && secret.required === true,
|
|
1067
|
+
);
|
|
1068
|
+
if (!declared) {
|
|
1069
|
+
throw new ValidationError(
|
|
1070
|
+
`Provider "${config.id}" requires ${vendor} egress but does not declare ${secretName}.`,
|
|
1071
|
+
{
|
|
1072
|
+
fix: `Add secrets: [{ name: "${secretName}", required: true }] to the provider (every vendor in a required proxy chain must declare its credential secrets).`,
|
|
1073
|
+
},
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
// `decodo`/`custom` are deprecated vendor values (string-union members, so the
|
|
1080
|
+
// @deprecated symbol gate can't catch them — warn at validation time instead).
|
|
1081
|
+
const deprecatedVendors = vendorChain.filter(
|
|
1082
|
+
(vendor) => vendor === "decodo" || vendor === "custom",
|
|
1083
|
+
);
|
|
1084
|
+
if (
|
|
1085
|
+
proxy.mode === "required" &&
|
|
1086
|
+
vendorChain.length > 0 &&
|
|
1087
|
+
deprecatedVendors.length === vendorChain.length
|
|
1088
|
+
) {
|
|
1089
|
+
throw new ValidationError(
|
|
1090
|
+
`Provider "${config.id}" requires proxy egress but declares only deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}.`,
|
|
1091
|
+
{
|
|
1092
|
+
fix: `Use proxy.provider or proxy.providers with "smartproxy" and/or "nodemaven".`,
|
|
1093
|
+
},
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
if (deprecatedVendors.length > 0) {
|
|
1097
|
+
console.warn(
|
|
1098
|
+
`[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven".`,
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
449
1101
|
}
|
|
450
1102
|
|
|
451
1103
|
function validateProviderStt(config: { id: string; stt?: ProviderSttConfig }): void {
|
|
@@ -460,6 +1112,83 @@ function validateProviderStt(config: { id: string; stt?: ProviderSttConfig }): v
|
|
|
460
1112
|
assertLiteralField(stt.mode, "stt.mode", VALID_PROVIDER_STT_MODES, config.id);
|
|
461
1113
|
}
|
|
462
1114
|
|
|
1115
|
+
function validateProviderOcr(config: { id: string; ocr?: ProviderOcrConfig }): void {
|
|
1116
|
+
const ocr = config.ocr;
|
|
1117
|
+
if (ocr === undefined) return;
|
|
1118
|
+
if (!ocr || typeof ocr !== "object" || Array.isArray(ocr)) {
|
|
1119
|
+
throw new ValidationError(`Provider "${config.id}" has invalid ocr: must be an object.`, {
|
|
1120
|
+
fix: `Use ocr: { mode: "required" } or ocr: { mode: "optional" }.`,
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
rejectUnknownFields(ocr, new Set(["mode"]), "ocr");
|
|
1124
|
+
assertLiteralField(ocr.mode, "ocr.mode", VALID_PROVIDER_OCR_MODES, config.id);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function validateProviderResolver(config: { id: string; resolver?: ProviderResolverConfig }): void {
|
|
1128
|
+
const resolver = config.resolver;
|
|
1129
|
+
if (resolver === undefined) return;
|
|
1130
|
+
if (!resolver || typeof resolver !== "object" || Array.isArray(resolver)) {
|
|
1131
|
+
throw new ValidationError(`Provider "${config.id}" has invalid resolver: must be an object.`, {
|
|
1132
|
+
fix: `Set resolver for provider "${config.id}" to { vendors: ["2captcha"], kinds: ["turnstile"] }.`,
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
rejectUnknownFields(
|
|
1136
|
+
resolver,
|
|
1137
|
+
new Set(["vendors", "kinds", "clientProfile"]),
|
|
1138
|
+
"resolver",
|
|
1139
|
+
config.id,
|
|
1140
|
+
);
|
|
1141
|
+
if (resolver.vendors !== undefined) {
|
|
1142
|
+
validateResolverLiteralArray(
|
|
1143
|
+
resolver.vendors,
|
|
1144
|
+
"resolver.vendors",
|
|
1145
|
+
VALID_PROVIDER_RESOLVER_VENDORS,
|
|
1146
|
+
config.id,
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
validateResolverLiteralArray(
|
|
1150
|
+
resolver.kinds,
|
|
1151
|
+
"resolver.kinds",
|
|
1152
|
+
VALID_PROVIDER_CHALLENGE_KINDS,
|
|
1153
|
+
config.id,
|
|
1154
|
+
);
|
|
1155
|
+
if (
|
|
1156
|
+
resolver.clientProfile !== undefined &&
|
|
1157
|
+
(typeof resolver.clientProfile !== "string" || !resolver.clientProfile.trim())
|
|
1158
|
+
) {
|
|
1159
|
+
throw new ValidationError(
|
|
1160
|
+
`Provider "${config.id}" has invalid resolver.clientProfile: must be a non-empty string.`,
|
|
1161
|
+
{
|
|
1162
|
+
fix: `Set resolver.clientProfile for provider "${config.id}" to a transport-owned profile name.`,
|
|
1163
|
+
},
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function validateResolverLiteralArray<TValue extends string>(
|
|
1169
|
+
value: readonly TValue[],
|
|
1170
|
+
field: string,
|
|
1171
|
+
validValues: readonly TValue[],
|
|
1172
|
+
providerId: string,
|
|
1173
|
+
): void {
|
|
1174
|
+
if (!Array.isArray(value)) {
|
|
1175
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an array.`, {
|
|
1176
|
+
fix: `Set ${field} for provider "${providerId}" to an array containing only: ${validValues.join(", ")}.`,
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
for (const [index, item] of value.entries()) {
|
|
1180
|
+
if (typeof item === "string" && validValues.some((validValue) => validValue === item)) {
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
throw new ValidationError(
|
|
1184
|
+
`Provider "${providerId}" has invalid ${field}[${index}]: ${JSON.stringify(item)}. Expected one of: ${validValues.join(", ")}`,
|
|
1185
|
+
{
|
|
1186
|
+
fix: `Set ${field}[${index}] for provider "${providerId}" to one of ${validValues.map((validValue) => `"${validValue}"`).join(", ")}.`,
|
|
1187
|
+
},
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
463
1192
|
function validateOperationIds(
|
|
464
1193
|
providerId: string,
|
|
465
1194
|
operations: Record<string, ProviderOperation>,
|
|
@@ -702,6 +1431,36 @@ function validateOperationObservability(
|
|
|
702
1431
|
}
|
|
703
1432
|
}
|
|
704
1433
|
|
|
1434
|
+
function validateOperationErrorCodes(
|
|
1435
|
+
providerId: string,
|
|
1436
|
+
operations: Record<string, ProviderOperation>,
|
|
1437
|
+
): void {
|
|
1438
|
+
for (const [operationName, operation] of Object.entries(operations)) {
|
|
1439
|
+
for (const [index, errorCode] of (operation.docs?.errorCodes ?? []).entries()) {
|
|
1440
|
+
if (
|
|
1441
|
+
errorCode.status !== undefined &&
|
|
1442
|
+
!VALID_OPERATION_ERROR_STATUSES.some((status) => status === errorCode.status)
|
|
1443
|
+
) {
|
|
1444
|
+
const field = `operations.${operationName}.docs.errorCodes[${index}].status`;
|
|
1445
|
+
throw new ValidationError(
|
|
1446
|
+
`Provider "${providerId}" has invalid ${field}: ${String(errorCode.status)} is not an emittable provider error status.`,
|
|
1447
|
+
{
|
|
1448
|
+
fix: `Set ${field} to one of ${VALID_OPERATION_ERROR_STATUSES.join(", ")}, or omit it.`,
|
|
1449
|
+
},
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
if (
|
|
1453
|
+
errorCode.status !== undefined &&
|
|
1454
|
+
SDK_RUNTIME_OWNED_ERROR_CODES.has(errorCode.code)
|
|
1455
|
+
) {
|
|
1456
|
+
console.warn(
|
|
1457
|
+
`[provider-sdk] Provider "${providerId}" operation "${operationName}" declares status ${errorCode.status} for SDK-owned error code "${errorCode.code}"; the declared status is documentation-only and will be ignored at runtime.`,
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
|
|
705
1464
|
const JSON_TRANSPORT_FIELDS = new Set(["kind"]);
|
|
706
1465
|
const SSE_TRANSPORT_FIELDS = new Set([
|
|
707
1466
|
"kind",
|
|
@@ -972,6 +1731,7 @@ const HEALTH_CHECK_CASE_FIELDS = new Set([
|
|
|
972
1731
|
"input",
|
|
973
1732
|
"prepareInput",
|
|
974
1733
|
"assertions",
|
|
1734
|
+
"scenario",
|
|
975
1735
|
"degradedThresholdMs",
|
|
976
1736
|
"timeoutMs",
|
|
977
1737
|
"expectedStatus",
|
|
@@ -1027,7 +1787,12 @@ function suggestField(unknown: string, candidates: ReadonlySet<string>): string
|
|
|
1027
1787
|
return best;
|
|
1028
1788
|
}
|
|
1029
1789
|
|
|
1030
|
-
function rejectUnknownFields(
|
|
1790
|
+
function rejectUnknownFields(
|
|
1791
|
+
value: object,
|
|
1792
|
+
allowed: ReadonlySet<string>,
|
|
1793
|
+
fieldPath: string,
|
|
1794
|
+
providerId?: string,
|
|
1795
|
+
): void {
|
|
1031
1796
|
for (const key of Object.keys(value)) {
|
|
1032
1797
|
if (allowed.has(key)) continue;
|
|
1033
1798
|
const hint = suggestField(key, allowed);
|
|
@@ -1035,7 +1800,11 @@ function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldP
|
|
|
1035
1800
|
hint
|
|
1036
1801
|
? `Unknown field "${key}" on ${fieldPath}. Did you mean "${hint}"?`
|
|
1037
1802
|
: `Unknown field "${key}" on ${fieldPath}.`,
|
|
1038
|
-
{
|
|
1803
|
+
{
|
|
1804
|
+
fix: providerId
|
|
1805
|
+
? `Remove ${fieldPath}.${key} from provider "${providerId}" or rename it.`
|
|
1806
|
+
: `Remove ${fieldPath}.${key} or rename it.`,
|
|
1807
|
+
},
|
|
1039
1808
|
);
|
|
1040
1809
|
}
|
|
1041
1810
|
}
|
|
@@ -1212,7 +1981,19 @@ function validateHealthCheckCase(
|
|
|
1212
1981
|
throw new ValidationError(
|
|
1213
1982
|
`Provider "${providerId}" ${fieldPath}.name must be a non-empty string.`,
|
|
1214
1983
|
);
|
|
1215
|
-
|
|
1984
|
+
const hasScenario = c.scenario !== undefined;
|
|
1985
|
+
const imperativeFields = [
|
|
1986
|
+
...(c.prepareInput === undefined ? [] : ["prepareInput"]),
|
|
1987
|
+
...(c.assertions === undefined ? [] : ["assertions"]),
|
|
1988
|
+
];
|
|
1989
|
+
if (hasScenario && imperativeFields.length > 0)
|
|
1990
|
+
throw new ValidationError(
|
|
1991
|
+
`Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" cannot declare scenario with ${imperativeFields.join(" and ")}.`,
|
|
1992
|
+
{
|
|
1993
|
+
fix: `Remove ${imperativeFields.map((field) => `${fieldPath}.${field}`).join(" and ")} and keep ${fieldPath}.scenario, or remove ${fieldPath}.scenario to keep the imperative hooks.`,
|
|
1994
|
+
},
|
|
1995
|
+
);
|
|
1996
|
+
if (!hasScenario && typeof c.assertions !== "function")
|
|
1216
1997
|
throw new ValidationError(
|
|
1217
1998
|
`Provider "${providerId}" ${fieldPath}.assertions must be a function.`,
|
|
1218
1999
|
{
|
|
@@ -1223,6 +2004,24 @@ function validateHealthCheckCase(
|
|
|
1223
2004
|
throw new ValidationError(
|
|
1224
2005
|
`Provider "${providerId}" ${fieldPath}.prepareInput must be a function.`,
|
|
1225
2006
|
);
|
|
2007
|
+
if (hasScenario) {
|
|
2008
|
+
const parsedScenario = HealthScenarioSchema.safeParse(c.scenario);
|
|
2009
|
+
if (!parsedScenario.success)
|
|
2010
|
+
throw new ValidationError(
|
|
2011
|
+
`Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" has an invalid scenario: it must conform to HealthScenario.`,
|
|
2012
|
+
{ fix: `Build ${fieldPath}.scenario with defineHealthScenario().` },
|
|
2013
|
+
);
|
|
2014
|
+
const unrelatedOperation = parsedScenario.data.coversOperations.find(
|
|
2015
|
+
(operationId) => operationId !== operationName,
|
|
2016
|
+
);
|
|
2017
|
+
if (unrelatedOperation !== undefined)
|
|
2018
|
+
throw new ValidationError(
|
|
2019
|
+
`Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" scenario.coversOperations cannot claim unrelated operation "${unrelatedOperation}".`,
|
|
2020
|
+
{
|
|
2021
|
+
fix: `Set ${fieldPath}.scenario.coversOperations to ["${operationName}"].`,
|
|
2022
|
+
},
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
1226
2025
|
if (
|
|
1227
2026
|
c.degradedThresholdMs !== undefined &&
|
|
1228
2027
|
(typeof c.degradedThresholdMs !== "number" ||
|
|
@@ -1372,6 +2171,7 @@ const HEALTH_JOURNEY_FIELDS = new Set([
|
|
|
1372
2171
|
"manualTrigger",
|
|
1373
2172
|
"steps",
|
|
1374
2173
|
"run",
|
|
2174
|
+
"scenario",
|
|
1375
2175
|
]);
|
|
1376
2176
|
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set(["kind", "interval", "jitter", "randomize"]);
|
|
1377
2177
|
const HEALTH_JOURNEY_STEP_FIELDS = new Set([
|
|
@@ -1907,6 +2707,14 @@ function validateHealthJourneys(
|
|
|
1907
2707
|
}
|
|
1908
2708
|
if (journey.manualTrigger !== undefined)
|
|
1909
2709
|
validateHealthJourneyManualTrigger(providerId, journey.id, journey.manualTrigger);
|
|
2710
|
+
if (journey.scenario !== undefined && journey.smsMatchers !== undefined)
|
|
2711
|
+
throw new ValidationError(
|
|
2712
|
+
`Provider "${providerId}" healthJourneys.${journey.id}.smsMatchers is not allowed on declarative scenarios.`,
|
|
2713
|
+
);
|
|
2714
|
+
if (journey.scenario !== undefined && journey.requiredSecrets !== undefined)
|
|
2715
|
+
throw new ValidationError(
|
|
2716
|
+
`Provider "${providerId}" healthJourneys.${journey.id}.requiredSecrets is not allowed on declarative scenarios.`,
|
|
2717
|
+
);
|
|
1910
2718
|
if (journey.timeout !== undefined)
|
|
1911
2719
|
assertIsoDuration(
|
|
1912
2720
|
journey.timeout,
|
|
@@ -1977,6 +2785,26 @@ function validateOperationFixtures(
|
|
|
1977
2785
|
fix: `Add operations.${operationName}.handler as an async function with signature (ctx, input) => Promise<output>`,
|
|
1978
2786
|
},
|
|
1979
2787
|
);
|
|
2788
|
+
if (operation.fixtures?.recordedAt !== undefined) {
|
|
2789
|
+
const recordedAt = operation.fixtures.recordedAt;
|
|
2790
|
+
const parsed =
|
|
2791
|
+
typeof recordedAt === "string"
|
|
2792
|
+
? new Date(`${recordedAt}T00:00:00.000Z`)
|
|
2793
|
+
: new Date(Number.NaN);
|
|
2794
|
+
const isCalendarDate =
|
|
2795
|
+
typeof recordedAt === "string" &&
|
|
2796
|
+
/^\d{4}-\d{2}-\d{2}$/.test(recordedAt) &&
|
|
2797
|
+
!Number.isNaN(parsed.getTime()) &&
|
|
2798
|
+
parsed.toISOString().slice(0, 10) === recordedAt;
|
|
2799
|
+
const kstToday = new Date(Date.now() + 9 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
2800
|
+
if (!isCalendarDate || recordedAt > kstToday)
|
|
2801
|
+
throw new ValidationError(
|
|
2802
|
+
`Fixture recordedAt must be a valid, non-future KST calendar date for provider "${providerId}" operation "${operationName}"`,
|
|
2803
|
+
{
|
|
2804
|
+
fix: `Set operations.${operationName}.fixtures.recordedAt to the KST capture date in YYYY-MM-DD format; it must not be in the future.`,
|
|
2805
|
+
},
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
1980
2808
|
if (operation.fixtures?.request !== undefined) {
|
|
1981
2809
|
const result = safeParseSchemaSync(
|
|
1982
2810
|
operation.input,
|
|
@@ -2010,6 +2838,31 @@ function validateOperationFixtures(
|
|
|
2010
2838
|
}
|
|
2011
2839
|
}
|
|
2012
2840
|
|
|
2841
|
+
function resolveOperationFixtureRequests<TOperations extends Record<string, ProviderOperation>>(
|
|
2842
|
+
operations: TOperations,
|
|
2843
|
+
): TOperations {
|
|
2844
|
+
let changed = false;
|
|
2845
|
+
const resolvedOperations = Object.fromEntries(
|
|
2846
|
+
Object.entries(operations).map(([operationName, operation]) => {
|
|
2847
|
+
if (operation.fixtures?.request === undefined) return [operationName, operation];
|
|
2848
|
+
const request = resolveHealthCheckInputDateTokens(operation.fixtures.request);
|
|
2849
|
+
if (request === operation.fixtures.request) return [operationName, operation];
|
|
2850
|
+
changed = true;
|
|
2851
|
+
return [
|
|
2852
|
+
operationName,
|
|
2853
|
+
{
|
|
2854
|
+
...operation,
|
|
2855
|
+
fixtures: {
|
|
2856
|
+
...operation.fixtures,
|
|
2857
|
+
request,
|
|
2858
|
+
},
|
|
2859
|
+
},
|
|
2860
|
+
];
|
|
2861
|
+
}),
|
|
2862
|
+
) as TOperations;
|
|
2863
|
+
return changed ? resolvedOperations : operations;
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2013
2866
|
/**
|
|
2014
2867
|
* Shallow shape guard only: the `deployment` object is passed through
|
|
2015
2868
|
* verbatim and deliberately not deep-validated by the SDK — the APIFuse
|
|
@@ -2023,33 +2876,47 @@ function validateProviderDeployment(providerId: string, deployment: unknown): vo
|
|
|
2023
2876
|
});
|
|
2024
2877
|
}
|
|
2025
2878
|
|
|
2026
|
-
|
|
2879
|
+
/** The second authoring phase for a declaration established by defineProvider. */
|
|
2880
|
+
export type ProviderBuilder<TDeclaration extends ProviderDeclaration> = <
|
|
2027
2881
|
TOperations extends Record<string, ProviderOperation>,
|
|
2028
|
-
TConfig extends ProviderConfig<TOperations>,
|
|
2029
2882
|
>(
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2883
|
+
implementation: {
|
|
2884
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
2885
|
+
},
|
|
2886
|
+
) => Omit<ProviderDefinition, "operations"> & {
|
|
2887
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
2888
|
+
};
|
|
2889
|
+
|
|
2890
|
+
/** Extract the declaration-derived operation context from a provider builder. */
|
|
2891
|
+
export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer TDeclaration>
|
|
2892
|
+
? ProviderContextFor<TDeclaration>
|
|
2893
|
+
: never;
|
|
2894
|
+
|
|
2895
|
+
/** Establish a provider declaration before its operations are contextually typed. */
|
|
2896
|
+
export function defineProvider<const TDeclaration extends ProviderDeclaration>(
|
|
2897
|
+
declaration: TDeclaration &
|
|
2898
|
+
Record<Exclude<keyof TDeclaration, keyof ProviderDeclaration>, never> &
|
|
2899
|
+
AuthStartNoInputGuard<TDeclaration>,
|
|
2900
|
+
): ProviderBuilder<TDeclaration> {
|
|
2901
|
+
validateProviderDeclaration(declaration);
|
|
2902
|
+
const buildProvider = <TOperations extends Record<string, ProviderOperation>>(
|
|
2903
|
+
implementation: {
|
|
2904
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
2905
|
+
},
|
|
2906
|
+
) =>
|
|
2907
|
+
finalizeProvider({
|
|
2908
|
+
...declaration,
|
|
2909
|
+
...implementation,
|
|
2910
|
+
} as ProviderConfig<TOperations, ProviderContextFor<TDeclaration>>);
|
|
2911
|
+
return buildProvider as ProviderBuilder<TDeclaration>;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
function validateProviderDeclaration(config: ProviderDeclaration): void {
|
|
2915
|
+
validateProviderDeclarationShape(config);
|
|
2033
2916
|
if (!CONNECTOR_ID_REGEX.test(config.id))
|
|
2034
2917
|
throw new ProviderError(`Invalid provider id: "${config.id}"`, {
|
|
2035
2918
|
fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
|
|
2036
2919
|
});
|
|
2037
|
-
if (Object.keys(config.operations).length === 0)
|
|
2038
|
-
throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
|
|
2039
|
-
fix: "Add at least one operation to the operations object",
|
|
2040
|
-
});
|
|
2041
|
-
validateOperationIds(config.id, config.operations);
|
|
2042
|
-
validateOperationAnnotations(config.id, config.operations);
|
|
2043
|
-
validateOperationObservability(config.id, config.operations);
|
|
2044
|
-
validateOperationTransports(config.id, config.operations);
|
|
2045
|
-
validateOperationContracts(config.id, config.operations);
|
|
2046
|
-
validateToolRouterMetadata(config.id, config.operations);
|
|
2047
|
-
const journeyCoveredOperations = validateHealthJourneys(
|
|
2048
|
-
config.id,
|
|
2049
|
-
config.operations,
|
|
2050
|
-
config.healthJourneys,
|
|
2051
|
-
);
|
|
2052
|
-
validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
|
|
2053
2920
|
if (config.healthMonitor !== undefined && config.healthProbe !== undefined)
|
|
2054
2921
|
throw new ValidationError(
|
|
2055
2922
|
`Provider "${config.id}" declares both healthMonitor and healthProbe. They are aliases; declare exactly one.`,
|
|
@@ -2062,10 +2929,18 @@ export function defineProvider<
|
|
|
2062
2929
|
config.healthProbe ?? config.healthMonitor,
|
|
2063
2930
|
config.healthProbe !== undefined ? "healthProbe" : "healthMonitor",
|
|
2064
2931
|
);
|
|
2065
|
-
validateOperationFixtures(config.id, config.operations);
|
|
2066
2932
|
validateProviderDeployment(config.id, config.deployment);
|
|
2933
|
+
try {
|
|
2934
|
+
validateNativeProviderConfig(config.native);
|
|
2935
|
+
} catch (error) {
|
|
2936
|
+
if (error instanceof NativeEgressPolicyValidationError)
|
|
2937
|
+
throw new ValidationError(error.message);
|
|
2938
|
+
throw error;
|
|
2939
|
+
}
|
|
2067
2940
|
validateProviderProxy(config);
|
|
2941
|
+
validateProviderOcr(config);
|
|
2068
2942
|
validateProviderStt(config);
|
|
2943
|
+
validateProviderResolver(config);
|
|
2069
2944
|
if (config.runtime === "browser" && !config.browser)
|
|
2070
2945
|
throw new ProviderError(
|
|
2071
2946
|
`Provider "${config.id}" must define browser.engine when runtime is "browser"`,
|
|
@@ -2078,7 +2953,40 @@ export function defineProvider<
|
|
|
2078
2953
|
`Provider "${config.id}" cannot define browser config unless runtime is "browser"`,
|
|
2079
2954
|
{ fix: 'Set runtime: "browser" or remove the browser config' },
|
|
2080
2955
|
);
|
|
2081
|
-
|
|
2956
|
+
validateFailClosedProviderDeclaration(config);
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
function finalizeProvider<
|
|
2960
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
2961
|
+
TContext,
|
|
2962
|
+
>(
|
|
2963
|
+
config: ProviderConfig<TOperations, TContext>,
|
|
2964
|
+
): Omit<ProviderDefinition, "operations"> & {
|
|
2965
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
2966
|
+
} {
|
|
2967
|
+
validateProviderImplementationShape(config);
|
|
2968
|
+
const operations = resolveOperationFixtureRequests(config.operations);
|
|
2969
|
+
if (Object.keys(config.operations).length === 0)
|
|
2970
|
+
throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
|
|
2971
|
+
fix: "Add at least one operation to the operations object",
|
|
2972
|
+
});
|
|
2973
|
+
validateOperationIds(config.id, config.operations);
|
|
2974
|
+
validateOperationAnnotations(config.id, config.operations);
|
|
2975
|
+
validateOperationObservability(config.id, config.operations);
|
|
2976
|
+
validateOperationErrorCodes(config.id, config.operations);
|
|
2977
|
+
validateOperationTransports(config.id, config.operations);
|
|
2978
|
+
validateOperationContracts(config.id, config.operations);
|
|
2979
|
+
validateToolRouterMetadata(config.id, config.operations);
|
|
2980
|
+
const journeyCoveredOperations = validateHealthJourneys(
|
|
2981
|
+
config.id,
|
|
2982
|
+
config.operations,
|
|
2983
|
+
config.healthJourneys,
|
|
2984
|
+
);
|
|
2985
|
+
validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
|
|
2986
|
+
validateOperationFixtures(config.id, operations);
|
|
2987
|
+
const provider: Omit<ProviderDefinition, "operations"> & {
|
|
2988
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
2989
|
+
} = {
|
|
2082
2990
|
id: config.id,
|
|
2083
2991
|
version: config.version,
|
|
2084
2992
|
runtime: config.runtime,
|
|
@@ -2086,9 +2994,12 @@ export function defineProvider<
|
|
|
2086
2994
|
// are owned by the APIFuse registry builder, not the SDK.
|
|
2087
2995
|
deployment: config.deployment,
|
|
2088
2996
|
allowedHosts: config.allowedHosts,
|
|
2997
|
+
native: config.native,
|
|
2089
2998
|
stealth: config.stealth,
|
|
2090
2999
|
proxy: config.proxy,
|
|
3000
|
+
ocr: config.ocr,
|
|
2091
3001
|
stt: config.stt,
|
|
3002
|
+
resolver: config.resolver,
|
|
2092
3003
|
browser: config.browser,
|
|
2093
3004
|
auth: config.auth,
|
|
2094
3005
|
reviewed: config.reviewed,
|
|
@@ -2097,11 +3008,15 @@ export function defineProvider<
|
|
|
2097
3008
|
credential: config.credential,
|
|
2098
3009
|
context: config.context,
|
|
2099
3010
|
meta: config.meta,
|
|
2100
|
-
operations
|
|
3011
|
+
operations,
|
|
2101
3012
|
// Transitional healthMonitor → healthProbe alias: mirror whichever field
|
|
2102
3013
|
// was declared onto both so old and new consumers keep working.
|
|
2103
3014
|
healthMonitor: config.healthMonitor ?? config.healthProbe,
|
|
2104
3015
|
healthProbe: config.healthProbe ?? config.healthMonitor,
|
|
2105
3016
|
healthJourneys: config.healthJourneys,
|
|
2106
3017
|
};
|
|
3018
|
+
// Declaration validation never invokes handlers, so their declaration-bound
|
|
3019
|
+
// context parameter is irrelevant to the runtime ProviderDefinition shape.
|
|
3020
|
+
validateFailClosedOperationDeclaration(provider as unknown as ProviderDefinition);
|
|
3021
|
+
return provider;
|
|
2107
3022
|
}
|