@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.50
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 +537 -33
- package/CHANGELOG.md +201 -1
- package/README.md +83 -22
- package/SUBMISSION.md +2 -2
- package/bin/apifuse-check.ts +165 -11
- package/bin/apifuse-dev.ts +57 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +357 -38
- package/bin/apifuse-perf.ts +14 -13
- package/bin/apifuse-record.ts +709 -72
- package/bin/apifuse-submit-check.ts +2344 -324
- 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 +27 -0
- package/dist/cli/create.d.ts +4 -1
- package/dist/cli/create.js +42 -37
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1178 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +52 -0
- package/dist/cli/migrate-provider-shape.js +578 -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 +5 -5
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +179 -16
- package/dist/config/loader.js +435 -127
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +12 -5
- package/dist/contract.js +24 -10
- package/dist/declaration-validation.d.ts +33 -0
- package/dist/declaration-validation.js +266 -0
- package/dist/define.d.ts +66 -29
- package/dist/define.js +778 -69
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +1 -1
- package/dist/engine.d.ts +78 -0
- package/dist/engine.js +133 -0
- 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 +21 -10
- package/dist/index.js +12 -7
- package/dist/lint.d.ts +23 -11
- package/dist/lint.js +572 -69
- 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 +10 -2
- package/dist/provider.js +4 -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/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +26 -25
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +515 -53
- package/dist/runtime/insights.js +2 -2
- 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/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +397 -16
- 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 +14 -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 +531 -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 +408 -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 +72 -0
- package/dist/runtime/resolver.js +771 -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 +41 -5
- package/dist/runtime/stealth.js +997 -291
- 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 +62 -0
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +43 -10
- 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 +5 -2
- package/dist/server/index.js +2 -2
- 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 +14 -3
- package/dist/server/self-test.js +129 -58
- package/dist/server/serve-implementation.d.ts +230 -0
- package/dist/server/serve-implementation.js +2265 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1131
- package/dist/server/trace-output.d.ts +6 -0
- package/dist/server/trace-output.js +79 -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.d.ts +9 -4
- package/dist/stealth/profiles.js +160 -211
- 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 +673 -131
- package/dist/types.js +1 -0
- package/package.json +45 -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/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
- package/src/cli/commands.ts +33 -0
- package/src/cli/create.ts +50 -37
- package/src/cli/migrate-operation-declaration.ts +1654 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +772 -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 +5 -5
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +690 -163
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +12 -5
- package/src/contract.ts +24 -10
- package/src/declaration-validation.ts +330 -0
- package/src/define.ts +1048 -131
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- 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 +220 -13
- package/src/lint.ts +698 -90
- 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 +161 -3
- 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/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +43 -31
- package/src/runtime/http.ts +641 -61
- package/src/runtime/insights.ts +2 -2
- 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/otlp.ts +467 -21
- 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 +23 -0
- package/src/runtime/resolver-shared.ts +33 -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 +707 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
- package/src/runtime/resolver-vendors/types.ts +212 -0
- package/src/runtime/resolver.ts +1157 -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 +1280 -336
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +57 -17
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/index.ts +34 -2
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test.ts +191 -68
- package/src/server/serve-implementation.ts +3378 -0
- package/src/server/serve.ts +1 -1632
- package/src/server/trace-output.ts +129 -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 +202 -231
- 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 +788 -174
- 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,19 @@
|
|
|
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 { isEngineOwnedProxyCredentialName, isEngineOwnedTelemetryEnvName } from "./engine.js";
|
|
10
|
+
import { HealthScenarioSchema } from "./health-scenario.js";
|
|
11
|
+
import {
|
|
12
|
+
NativeEgressPolicyValidationError,
|
|
13
|
+
validateNativeProviderConfig,
|
|
14
|
+
} from "./native-egress-policy.js";
|
|
4
15
|
import { safeParseSchemaSync } from "./schema.js";
|
|
16
|
+
import { resolveHealthCheckInputDateTokens } from "./server/self-test-input-tokens.js";
|
|
5
17
|
import type {
|
|
6
18
|
AuthConfig,
|
|
7
19
|
BrowserEngine,
|
|
@@ -14,6 +26,7 @@ import type {
|
|
|
14
26
|
HealthJourneySchedule,
|
|
15
27
|
HealthScheduleRandomization,
|
|
16
28
|
InferSchemaOutput,
|
|
29
|
+
NativeProviderConfig,
|
|
17
30
|
OperationDefinition,
|
|
18
31
|
OperationHandlerResult,
|
|
19
32
|
OperationHttpStreamTransport,
|
|
@@ -21,18 +34,24 @@ import type {
|
|
|
21
34
|
OperationTransport,
|
|
22
35
|
OperationWebSocketTransport,
|
|
23
36
|
ProviderAccessConfig,
|
|
37
|
+
ProviderChallengeKind,
|
|
38
|
+
ProviderContext,
|
|
24
39
|
ProviderDefinition,
|
|
25
40
|
ProviderDeploymentOverrides,
|
|
26
41
|
ProviderHealthMonitorConfig,
|
|
42
|
+
ProviderOcrConfig,
|
|
27
43
|
ProviderProxyConfig,
|
|
28
44
|
ProviderPublicProfile,
|
|
45
|
+
ProviderResolverConfig,
|
|
46
|
+
ProviderResolverVendor,
|
|
29
47
|
ProviderReviewed,
|
|
48
|
+
ProviderRuntimeTarget,
|
|
30
49
|
ProviderSecretDeclaration,
|
|
31
50
|
ProviderStreamEvent,
|
|
32
51
|
ProviderSttConfig,
|
|
33
52
|
SchemaLike,
|
|
34
53
|
SmsOtpMatcherDefinition,
|
|
35
|
-
|
|
54
|
+
StealthProfileSelection,
|
|
36
55
|
} from "./types.js";
|
|
37
56
|
import {
|
|
38
57
|
HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX,
|
|
@@ -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,22 +95,78 @@ 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
|
|
98
|
+
const VALID_RUNTIME_TARGETS = ["vanilla", "engine"] as const;
|
|
99
|
+
const VALID_AUTH_MODES = [
|
|
100
|
+
"none",
|
|
101
|
+
"platform-managed",
|
|
102
|
+
"credentials",
|
|
103
|
+
"oauth2",
|
|
104
|
+
"oauth2_proxied",
|
|
105
|
+
] as const;
|
|
106
|
+
const PROXIED_OAUTH_REQUIRED_FIELDS = [
|
|
107
|
+
"authorizeUrl",
|
|
108
|
+
"tokenUrl",
|
|
109
|
+
"customScheme",
|
|
110
|
+
"rewriteProfile",
|
|
111
|
+
"clientIdEnvKey",
|
|
112
|
+
] as const;
|
|
113
|
+
const PROXIED_OAUTH_ALLOWED_FIELDS = new Set([
|
|
114
|
+
...PROXIED_OAUTH_REQUIRED_FIELDS,
|
|
115
|
+
"pkce",
|
|
116
|
+
"authorizeParams",
|
|
117
|
+
"tokenParams",
|
|
118
|
+
]);
|
|
119
|
+
const PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS = new Set([
|
|
120
|
+
"client_id",
|
|
121
|
+
"response_type",
|
|
122
|
+
"state",
|
|
123
|
+
"code_challenge",
|
|
124
|
+
"code_challenge_method",
|
|
125
|
+
]);
|
|
126
|
+
const PROXIED_OAUTH_PROFILE_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
127
|
+
const PROXIED_OAUTH_ENV_KEY_REGEX = /^[A-Z][A-Z0-9_]*__[A-Z0-9_]+$/;
|
|
128
|
+
const CUSTOM_SCHEME_REGEX = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/\S+$/;
|
|
79
129
|
const VALID_PROVIDER_ACCESS_VISIBILITIES = ["public", "early_access"] as const;
|
|
80
130
|
const VALID_PROVIDER_PROXY_MODES = ["disabled", "optional", "required"] as const;
|
|
81
|
-
const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "decodo", "custom"] as const;
|
|
131
|
+
const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "nodemaven", "decodo", "custom"] as const;
|
|
82
132
|
const VALID_PROVIDER_PROXY_AFFINITIES = [
|
|
83
133
|
"request",
|
|
84
134
|
"operation",
|
|
85
135
|
"auth-flow",
|
|
86
136
|
"connection",
|
|
87
137
|
] as const;
|
|
138
|
+
const VALID_PROVIDER_OCR_MODES = ["optional", "required"] as const;
|
|
88
139
|
const VALID_PROVIDER_STT_MODES = ["optional", "required"] as const;
|
|
89
|
-
|
|
140
|
+
function exhaustiveLiteralArray<TUnion extends string>() {
|
|
141
|
+
return <const TValues extends readonly TUnion[]>(
|
|
142
|
+
values: TValues,
|
|
143
|
+
..._missing: Exclude<TUnion, TValues[number]> extends never
|
|
144
|
+
? []
|
|
145
|
+
: ["Missing runtime values", Exclude<TUnion, TValues[number]>]
|
|
146
|
+
): TValues => values;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const VALID_PROVIDER_RESOLVER_VENDORS = exhaustiveLiteralArray<ProviderResolverVendor>()([
|
|
150
|
+
"browser",
|
|
151
|
+
"capsolver",
|
|
152
|
+
"capmonster",
|
|
153
|
+
"2captcha",
|
|
154
|
+
"custom",
|
|
155
|
+
] as const);
|
|
156
|
+
export const VALID_PROVIDER_CHALLENGE_KINDS = exhaustiveLiteralArray<ProviderChallengeKind>()([
|
|
157
|
+
"turnstile",
|
|
158
|
+
"recaptcha_v2",
|
|
159
|
+
"recaptcha_v3",
|
|
160
|
+
"hcaptcha",
|
|
161
|
+
"cloudflare_interstitial",
|
|
162
|
+
"aws_waf",
|
|
163
|
+
"akamai_sec_cpt",
|
|
164
|
+
"akamai_sensor",
|
|
165
|
+
] as const);
|
|
90
166
|
const RESERVED_OPERATION_IDS = new Set(["auth", "health"]);
|
|
91
|
-
const MCP_TOOL_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_]{0,127}$/;
|
|
92
167
|
const VALID_OPERATION_RISK_CLASSES = ["read", "write", "destructive", "external-send"] as const;
|
|
93
168
|
const VALID_OPERATION_APPROVAL_POLICIES = ["never", "risk-based", "always"] as const;
|
|
169
|
+
const VALID_OPERATION_CONNECTION_MODES = ["none", "optional", "required"] as const;
|
|
94
170
|
const VALID_OPERATION_TRANSPORT_KINDS = ["json", "sse", "http-stream", "websocket"] as const;
|
|
95
171
|
const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
|
|
96
172
|
const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
@@ -114,74 +190,388 @@ function parsePositiveMsDuration(value: string): number | undefined {
|
|
|
114
190
|
return parsed;
|
|
115
191
|
}
|
|
116
192
|
|
|
117
|
-
type ProviderOperation = OperationDefinition<
|
|
118
|
-
type OperationConfig<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
193
|
+
type ProviderOperation = OperationDefinition<any, any, any>;
|
|
194
|
+
type OperationConfig<
|
|
195
|
+
TInput extends SchemaLike,
|
|
196
|
+
TOutput extends SchemaLike,
|
|
197
|
+
TContext = ProviderContext,
|
|
198
|
+
> = Omit<OperationDefinition<TInput, TOutput, TContext>, "handler"> & {
|
|
122
199
|
handler(
|
|
123
|
-
ctx:
|
|
200
|
+
ctx: TContext,
|
|
124
201
|
input: InferSchemaOutput<TInput>,
|
|
125
202
|
):
|
|
126
203
|
| OperationHandlerResult<InferSchemaOutput<TOutput>>
|
|
127
204
|
| Promise<OperationHandlerResult<InferSchemaOutput<TOutput>>>;
|
|
128
205
|
};
|
|
129
|
-
type OperationMapConfig<
|
|
206
|
+
type OperationMapConfig<
|
|
207
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
208
|
+
TContext = ProviderContext,
|
|
209
|
+
> = {
|
|
130
210
|
[K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput>
|
|
131
|
-
? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput>
|
|
211
|
+
? OperationConfig<TInput, TOutput, TContext> | OperationDefinition<TInput, TOutput, TContext>
|
|
132
212
|
: never;
|
|
133
213
|
};
|
|
134
|
-
type StreamOperationConfig<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
214
|
+
type StreamOperationConfig<
|
|
215
|
+
TInput extends SchemaLike,
|
|
216
|
+
TOutput extends SchemaLike,
|
|
217
|
+
TContext = ProviderContext,
|
|
218
|
+
> =
|
|
219
|
+
| SseOperationConfig<TInput, TOutput, TContext>
|
|
220
|
+
| HttpStreamOperationConfig<TInput, TOutput, TContext>
|
|
221
|
+
| WebSocketOperationConfig<TInput, TOutput, TContext>;
|
|
222
|
+
type SseOperationConfig<
|
|
223
|
+
TInput extends SchemaLike,
|
|
224
|
+
TOutput extends SchemaLike,
|
|
225
|
+
TContext = ProviderContext,
|
|
226
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
142
227
|
transport: OperationSseTransport;
|
|
143
228
|
handler(
|
|
144
|
-
ctx:
|
|
229
|
+
ctx: TContext,
|
|
145
230
|
input: InferSchemaOutput<TInput>,
|
|
146
231
|
): AsyncIterable<ProviderStreamEvent> | Promise<AsyncIterable<ProviderStreamEvent>>;
|
|
147
232
|
};
|
|
148
|
-
type HttpStreamOperationConfig<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
233
|
+
type HttpStreamOperationConfig<
|
|
234
|
+
TInput extends SchemaLike,
|
|
235
|
+
TOutput extends SchemaLike,
|
|
236
|
+
TContext = ProviderContext,
|
|
237
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
152
238
|
transport: OperationHttpStreamTransport;
|
|
153
239
|
handler(
|
|
154
|
-
ctx:
|
|
240
|
+
ctx: TContext,
|
|
155
241
|
input: InferSchemaOutput<TInput>,
|
|
156
242
|
): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
157
243
|
};
|
|
158
|
-
type WebSocketOperationConfig<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
244
|
+
type WebSocketOperationConfig<
|
|
245
|
+
TInput extends SchemaLike,
|
|
246
|
+
TOutput extends SchemaLike,
|
|
247
|
+
TContext = ProviderContext,
|
|
248
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
162
249
|
transport: OperationWebSocketTransport;
|
|
163
250
|
handler(
|
|
164
|
-
ctx:
|
|
251
|
+
ctx: TContext,
|
|
165
252
|
input: InferSchemaOutput<TInput>,
|
|
166
253
|
): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
167
254
|
};
|
|
168
255
|
|
|
169
|
-
type
|
|
256
|
+
type AuthStartHandlerNoInputGuard<TStart> = TStart extends (...args: infer TArgs) => unknown
|
|
257
|
+
? TArgs["length"] extends 0 | 1
|
|
258
|
+
? unknown
|
|
259
|
+
: {
|
|
260
|
+
"auth start handlers must not declare input parameters; return a form turn from start and receive user input in continue": never;
|
|
261
|
+
}
|
|
262
|
+
: unknown;
|
|
263
|
+
|
|
264
|
+
export type AuthStartNoInputGuard<TConfig> = TConfig extends {
|
|
170
265
|
auth?: { flow?: { start: infer TStart } };
|
|
171
266
|
}
|
|
172
|
-
? TStart
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
267
|
+
? AuthStartHandlerNoInputGuard<TStart>
|
|
268
|
+
: TConfig extends { start: infer TStart }
|
|
269
|
+
? AuthStartHandlerNoInputGuard<TStart>
|
|
270
|
+
: unknown;
|
|
271
|
+
|
|
272
|
+
function splitAuthStartParameters(parameters: string): string[] | undefined {
|
|
273
|
+
const parts: string[] = [];
|
|
274
|
+
let start = 0;
|
|
275
|
+
let round = 0;
|
|
276
|
+
let square = 0;
|
|
277
|
+
let curly = 0;
|
|
278
|
+
let quote: "'" | '"' | "`" | undefined;
|
|
279
|
+
let escaped = false;
|
|
280
|
+
let lineComment = false;
|
|
281
|
+
let blockComment = false;
|
|
282
|
+
const templateDepths = [0];
|
|
283
|
+
templateDepths.length = 0;
|
|
284
|
+
|
|
285
|
+
for (let index = 0; index < parameters.length; index++) {
|
|
286
|
+
const character = parameters[index];
|
|
287
|
+
const nextCharacter = parameters[index + 1];
|
|
288
|
+
if (lineComment) {
|
|
289
|
+
if (character === "\n" || character === "\r") lineComment = false;
|
|
290
|
+
else continue;
|
|
291
|
+
}
|
|
292
|
+
if (blockComment) {
|
|
293
|
+
if (character === "*" && nextCharacter === "/") {
|
|
294
|
+
blockComment = false;
|
|
295
|
+
index++;
|
|
296
|
+
}
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (quote) {
|
|
300
|
+
if (escaped) {
|
|
301
|
+
escaped = false;
|
|
302
|
+
} else if (character === "\\") {
|
|
303
|
+
escaped = true;
|
|
304
|
+
} else if (quote === "`" && character === "$" && nextCharacter === "{") {
|
|
305
|
+
curly++;
|
|
306
|
+
templateDepths.push(curly);
|
|
307
|
+
quote = undefined;
|
|
308
|
+
index++;
|
|
309
|
+
} else if (character === quote) {
|
|
310
|
+
quote = undefined;
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (character === "'" || character === '"' || character === "`") {
|
|
315
|
+
quote = character;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (character === "/" && nextCharacter === "/") {
|
|
319
|
+
lineComment = true;
|
|
320
|
+
index++;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (character === "/" && nextCharacter === "*") {
|
|
324
|
+
blockComment = true;
|
|
325
|
+
index++;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (character === "/") return undefined;
|
|
329
|
+
// Annex B HTML-like comments are not lexed here; give up rather than
|
|
330
|
+
// risk misreading the parameter list.
|
|
331
|
+
if (character === "<" && parameters.startsWith("!--", index + 1)) return undefined;
|
|
332
|
+
if (character === "-" && parameters.startsWith("->", index + 1)) return undefined;
|
|
333
|
+
if (character === "(") round++;
|
|
334
|
+
else if (character === ")") round--;
|
|
335
|
+
else if (character === "[") square++;
|
|
336
|
+
else if (character === "]") square--;
|
|
337
|
+
else if (character === "{") curly++;
|
|
338
|
+
else if (character === "}") {
|
|
339
|
+
if (templateDepths.at(-1) === curly) {
|
|
340
|
+
templateDepths.pop();
|
|
341
|
+
curly--;
|
|
342
|
+
quote = "`";
|
|
343
|
+
} else curly--;
|
|
344
|
+
} else if (character === "," && round === 0 && square === 0 && curly === 0) {
|
|
345
|
+
parts.push(parameters.slice(start, index));
|
|
346
|
+
start = index + 1;
|
|
347
|
+
}
|
|
348
|
+
if (round < 0 || square < 0 || curly < 0) return undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (
|
|
352
|
+
quote ||
|
|
353
|
+
blockComment ||
|
|
354
|
+
templateDepths.length > 0 ||
|
|
355
|
+
round !== 0 ||
|
|
356
|
+
square !== 0 ||
|
|
357
|
+
curly !== 0
|
|
358
|
+
)
|
|
359
|
+
return undefined;
|
|
360
|
+
parts.push(parameters.slice(start));
|
|
361
|
+
return parts;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function authStartParameterList(source: string): string | undefined {
|
|
365
|
+
let index = 0;
|
|
366
|
+
while (index < source.length && /\s/.test(source[index] ?? "")) index++;
|
|
367
|
+
if (index >= source.length) return undefined;
|
|
368
|
+
|
|
369
|
+
let openIndex = -1;
|
|
370
|
+
let parenthesizedArrow = false;
|
|
371
|
+
let asyncMethodOrArrow = false;
|
|
372
|
+
const skipTrivia = () => {
|
|
373
|
+
while (index < source.length) {
|
|
374
|
+
if (/\s/.test(source[index] ?? "")) {
|
|
375
|
+
index++;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (source[index] === "/" && source[index + 1] === "/") {
|
|
379
|
+
index += 2;
|
|
380
|
+
while (index < source.length && source[index] !== "\n" && source[index] !== "\r") index++;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (source[index] === "/" && source[index + 1] === "*") {
|
|
384
|
+
const end = source.indexOf("*/", index + 2);
|
|
385
|
+
if (end < 0) {
|
|
386
|
+
index = source.length;
|
|
387
|
+
return;
|
|
177
388
|
}
|
|
178
|
-
|
|
179
|
-
|
|
389
|
+
index = end + 2;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const initial = source.slice(index);
|
|
397
|
+
const isAsync = initial.startsWith("async") && !/[\w$]/.test(initial[5] ?? "");
|
|
398
|
+
if (isAsync) {
|
|
399
|
+
index += 5;
|
|
400
|
+
skipTrivia();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const afterAsync = source.slice(index);
|
|
404
|
+
const isFunction = afterAsync.startsWith("function") && !/[\w$]/.test(afterAsync[8] ?? "");
|
|
405
|
+
if (isFunction) {
|
|
406
|
+
index += 8;
|
|
407
|
+
skipTrivia();
|
|
408
|
+
if (source[index] === "*") {
|
|
409
|
+
index++;
|
|
410
|
+
skipTrivia();
|
|
411
|
+
}
|
|
412
|
+
} else if (source[index] === "*") {
|
|
413
|
+
index++;
|
|
414
|
+
skipTrivia();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (source[index] === "(") {
|
|
418
|
+
openIndex = index;
|
|
419
|
+
parenthesizedArrow = !isFunction;
|
|
420
|
+
asyncMethodOrArrow = isAsync && !isFunction;
|
|
421
|
+
} else if (isFunction) {
|
|
422
|
+
if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
|
|
423
|
+
index++;
|
|
424
|
+
while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
|
|
425
|
+
skipTrivia();
|
|
426
|
+
if (source[index] !== "(") return undefined;
|
|
427
|
+
openIndex = index;
|
|
428
|
+
} else {
|
|
429
|
+
const identifierStart = index;
|
|
430
|
+
if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
|
|
431
|
+
index++;
|
|
432
|
+
while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
|
|
433
|
+
const firstIdentifier = source.slice(identifierStart, index);
|
|
434
|
+
skipTrivia();
|
|
435
|
+
if (source[index] === "=" && source[index + 1] === ">") return undefined;
|
|
436
|
+
if (source[index] !== "(") {
|
|
437
|
+
if (firstIdentifier !== "get" && firstIdentifier !== "set") return undefined;
|
|
438
|
+
if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
|
|
439
|
+
index++;
|
|
440
|
+
while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
|
|
441
|
+
skipTrivia();
|
|
442
|
+
}
|
|
443
|
+
if (source[index] !== "(") return undefined;
|
|
444
|
+
openIndex = index;
|
|
445
|
+
}
|
|
446
|
+
if (openIndex < 0) return undefined;
|
|
447
|
+
|
|
448
|
+
let depth = 1;
|
|
449
|
+
let square = 0;
|
|
450
|
+
let curly = 0;
|
|
451
|
+
let quote: "'" | '"' | "`" | undefined;
|
|
452
|
+
let escaped = false;
|
|
453
|
+
let lineComment = false;
|
|
454
|
+
let blockComment = false;
|
|
455
|
+
const templateDepths = [0];
|
|
456
|
+
templateDepths.length = 0;
|
|
457
|
+
for (index = openIndex + 1; index < source.length; index++) {
|
|
458
|
+
const character = source[index];
|
|
459
|
+
const nextCharacter = source[index + 1];
|
|
460
|
+
if (lineComment) {
|
|
461
|
+
if (character === "\n" || character === "\r") lineComment = false;
|
|
462
|
+
else continue;
|
|
463
|
+
}
|
|
464
|
+
if (blockComment) {
|
|
465
|
+
if (character === "*" && nextCharacter === "/") {
|
|
466
|
+
blockComment = false;
|
|
467
|
+
index++;
|
|
468
|
+
}
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (quote) {
|
|
472
|
+
if (escaped) escaped = false;
|
|
473
|
+
else if (character === "\\") escaped = true;
|
|
474
|
+
else if (quote === "`" && character === "$" && nextCharacter === "{") {
|
|
475
|
+
curly++;
|
|
476
|
+
templateDepths.push(curly);
|
|
477
|
+
quote = undefined;
|
|
478
|
+
index++;
|
|
479
|
+
} else if (character === quote) quote = undefined;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (character === "'" || character === '"' || character === "`") {
|
|
483
|
+
quote = character;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (character === "/" && nextCharacter === "/") {
|
|
487
|
+
lineComment = true;
|
|
488
|
+
index++;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (character === "/" && nextCharacter === "*") {
|
|
492
|
+
blockComment = true;
|
|
493
|
+
index++;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (character === "/") return undefined;
|
|
497
|
+
// Annex B HTML-like comments are not lexed here; give up rather than
|
|
498
|
+
// risk misreading the parameter list.
|
|
499
|
+
if (character === "<" && source.startsWith("!--", index + 1)) return undefined;
|
|
500
|
+
if (character === "-" && source.startsWith("->", index + 1)) return undefined;
|
|
501
|
+
if (character === "(") depth++;
|
|
502
|
+
else if (character === ")") {
|
|
503
|
+
depth--;
|
|
504
|
+
if (depth === 0 && square === 0 && curly === 0) {
|
|
505
|
+
const closeIndex = index;
|
|
506
|
+
if (parenthesizedArrow) {
|
|
507
|
+
index++;
|
|
508
|
+
skipTrivia();
|
|
509
|
+
const hasArrow = source[index] === "=" && source[index + 1] === ">";
|
|
510
|
+
if (!hasArrow && (!asyncMethodOrArrow || source[index] !== "{")) return undefined;
|
|
511
|
+
}
|
|
512
|
+
return source.slice(openIndex + 1, closeIndex);
|
|
513
|
+
}
|
|
514
|
+
if (depth < 0) return undefined;
|
|
515
|
+
} else if (character === "[") square++;
|
|
516
|
+
else if (character === "]") {
|
|
517
|
+
square--;
|
|
518
|
+
if (square < 0) return undefined;
|
|
519
|
+
} else if (character === "{") curly++;
|
|
520
|
+
else if (character === "}") {
|
|
521
|
+
if (templateDepths.at(-1) === curly) {
|
|
522
|
+
templateDepths.pop();
|
|
523
|
+
curly--;
|
|
524
|
+
quote = "`";
|
|
525
|
+
} else curly--;
|
|
526
|
+
if (curly < 0) return undefined;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Conservative defense in depth for defaulted second parameters, which
|
|
534
|
+
* JavaScript intentionally omits from Function.length. Ambiguous source is
|
|
535
|
+
* ignored so this check can never reject a valid provider on weak evidence.
|
|
536
|
+
*/
|
|
537
|
+
function authStartHasHiddenInput(start: unknown): boolean {
|
|
538
|
+
let source: string;
|
|
539
|
+
try {
|
|
540
|
+
source = Function.prototype.toString.call(start);
|
|
541
|
+
} catch {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
if (
|
|
545
|
+
!source ||
|
|
546
|
+
source.includes("[native code]") ||
|
|
547
|
+
/^\s*(?:async\s+)?function\s+bound\b/.test(source)
|
|
548
|
+
)
|
|
549
|
+
return false;
|
|
550
|
+
|
|
551
|
+
const parameters = authStartParameterList(source);
|
|
552
|
+
if (!parameters) return false;
|
|
553
|
+
const parts = splitAuthStartParameters(parameters);
|
|
554
|
+
if (!parts || parts.length < 2) return false;
|
|
555
|
+
|
|
556
|
+
// Require ordinary, readable source formatting. This intentionally fails
|
|
557
|
+
// open for minified output and for transpilers that rewrite defaults.
|
|
558
|
+
const commaIndex = parameters.indexOf(",");
|
|
559
|
+
if (commaIndex < 0 || !/\s/.test(parameters[commaIndex + 1] ?? "")) return false;
|
|
560
|
+
const first = parts[0].trim();
|
|
561
|
+
const second = parts[1].trim();
|
|
562
|
+
const identifier = /^[_$A-Za-z][_$A-Za-z0-9]*/;
|
|
563
|
+
const firstName = first.match(identifier)?.[0];
|
|
564
|
+
const secondName = second.match(identifier)?.[0];
|
|
565
|
+
if (!firstName || !secondName || firstName.length < 3 || secondName.length < 3) return false;
|
|
566
|
+
return /\s=\s/.test(second);
|
|
567
|
+
}
|
|
180
568
|
|
|
181
|
-
export interface
|
|
569
|
+
export interface ProviderDeclaration {
|
|
182
570
|
id: string;
|
|
183
571
|
version: string;
|
|
184
572
|
runtime: "standard" | "shared" | "browser";
|
|
573
|
+
/** Provider business-logic target. Omit only while migrating a legacy provider. */
|
|
574
|
+
runtimeTarget?: ProviderRuntimeTarget;
|
|
185
575
|
/**
|
|
186
576
|
* Optional deployment overrides, passed through verbatim onto the returned
|
|
187
577
|
* provider definition. The SDK types this field but does not deep-validate
|
|
@@ -189,20 +579,36 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
189
579
|
* resolves omitted fields against the runtime deployment profiles.
|
|
190
580
|
*/
|
|
191
581
|
deployment?: ProviderDeploymentOverrides;
|
|
582
|
+
/** Declares the HTTP capability binding. A bare object states use without configuration. */
|
|
583
|
+
http?: Record<string, never> | true;
|
|
584
|
+
/** Declares upstream host policy; this does not add a `ctx.allowedHosts` member. */
|
|
192
585
|
allowedHosts?: string[];
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
};
|
|
586
|
+
native?: NativeProviderConfig;
|
|
587
|
+
stealth?: StealthProfileSelection;
|
|
588
|
+
/** Declares proxy policy; this is provider intent and does not add a `ctx.proxy` member. */
|
|
197
589
|
proxy?: ProviderProxyConfig;
|
|
590
|
+
ocr?: ProviderOcrConfig;
|
|
198
591
|
stt?: ProviderSttConfig;
|
|
592
|
+
resolver?: ProviderResolverConfig;
|
|
199
593
|
browser?: { engine: BrowserEngine };
|
|
200
594
|
auth?: AuthConfig;
|
|
595
|
+
/** Declares the choice capability binding. A bare object states use without configuration. */
|
|
596
|
+
choice?: Record<string, never> | true;
|
|
201
597
|
reviewed?: ProviderReviewed;
|
|
202
598
|
access?: ProviderAccessConfig;
|
|
599
|
+
/** Declares secret requirements; this does not add a `ctx.secrets` member. */
|
|
203
600
|
secrets?: ProviderSecretDeclaration[];
|
|
601
|
+
/** Declares the environment capability binding. A bare object states use without configuration. */
|
|
602
|
+
env?: Record<string, never> | true;
|
|
204
603
|
credential?: CredentialDeclaration;
|
|
604
|
+
/** Declares provider context metadata; this does not add a `ctx.context` member. */
|
|
205
605
|
context?: ContextDeclaration;
|
|
606
|
+
/** Declares the state capability binding. A bare object states use without configuration. */
|
|
607
|
+
state?: Record<string, never> | true;
|
|
608
|
+
/** Declares the cache capability binding. A bare object states use without configuration. */
|
|
609
|
+
cache?: Record<string, never> | true;
|
|
610
|
+
/** Declares the files capability binding. A bare object states use without configuration. */
|
|
611
|
+
files?: Record<string, never> | true;
|
|
206
612
|
meta: {
|
|
207
613
|
displayName: string;
|
|
208
614
|
displayNameKey?: string;
|
|
@@ -222,27 +628,41 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
222
628
|
implementationProfile?: ProviderImplementationProfile;
|
|
223
629
|
contract?: {
|
|
224
630
|
publicSchemaFieldNames?: "normalized";
|
|
631
|
+
readonly pinnedWireFieldPaths?: readonly {
|
|
632
|
+
readonly path: string;
|
|
633
|
+
readonly reason: string;
|
|
634
|
+
}[];
|
|
225
635
|
};
|
|
226
636
|
};
|
|
227
|
-
operations: OperationMapConfig<TOperations>;
|
|
228
637
|
healthMonitor?: ProviderHealthMonitorConfig;
|
|
229
638
|
/** New name for `healthMonitor` (transitional alias); declaring both is a ValidationError. */
|
|
230
639
|
healthProbe?: ProviderHealthMonitorConfig;
|
|
231
640
|
healthJourneys?: readonly HealthJourneyDefinition[];
|
|
232
641
|
}
|
|
233
642
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
643
|
+
interface ProviderConfig<
|
|
644
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
645
|
+
TContext = ProviderContext,
|
|
646
|
+
> extends ProviderDeclaration {
|
|
647
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
239
648
|
}
|
|
240
649
|
|
|
241
|
-
/** Define
|
|
242
|
-
export function
|
|
243
|
-
operation
|
|
244
|
-
|
|
245
|
-
|
|
650
|
+
/** Define one factored provider operation with schema-driven handler inference. */
|
|
651
|
+
export function defineOperation<TContext>() {
|
|
652
|
+
return function operation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
653
|
+
config: OperationConfig<TInput, TOutput, TContext>,
|
|
654
|
+
): OperationDefinition<TInput, TOutput, TContext> {
|
|
655
|
+
return config;
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** Define a factored non-JSON operation with explicit transport metadata. */
|
|
660
|
+
export function defineStreamOperation<TContext>() {
|
|
661
|
+
return function streamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
662
|
+
config: StreamOperationConfig<TInput, TOutput, TContext>,
|
|
663
|
+
): OperationDefinition<TInput, TOutput, TContext> {
|
|
664
|
+
return config;
|
|
665
|
+
};
|
|
246
666
|
}
|
|
247
667
|
|
|
248
668
|
function assertObjectConfig(value: unknown): asserts value is Record<string, unknown> {
|
|
@@ -279,18 +699,153 @@ function assertLiteralField<TValue extends string>(
|
|
|
279
699
|
);
|
|
280
700
|
}
|
|
281
701
|
}
|
|
282
|
-
|
|
702
|
+
|
|
703
|
+
function validateProxiedOAuthParams(
|
|
704
|
+
value: unknown,
|
|
705
|
+
field: "authorizeParams" | "tokenParams",
|
|
706
|
+
providerId: string,
|
|
707
|
+
): void {
|
|
708
|
+
if (value === undefined) return;
|
|
709
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
710
|
+
throw new ValidationError(
|
|
711
|
+
`Provider "${providerId}" auth.proxied.${field} must be an object of string values.`,
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
for (const [key, paramValue] of Object.entries(value)) {
|
|
715
|
+
if (!key.trim() || typeof paramValue !== "string") {
|
|
716
|
+
throw new ValidationError(
|
|
717
|
+
`Provider "${providerId}" auth.proxied.${field} must contain non-empty keys and string values.`,
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
if (
|
|
721
|
+
field === "authorizeParams" &&
|
|
722
|
+
PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())
|
|
723
|
+
) {
|
|
724
|
+
throw new ValidationError(
|
|
725
|
+
`Provider "${providerId}" auth.proxied.authorizeParams cannot override reserved parameter "${key}".`,
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function validateProxiedOAuthAuth(auth: Record<string, unknown>, providerId: string): void {
|
|
732
|
+
const proxied = auth.proxied;
|
|
733
|
+
if (auth.mode !== "oauth2_proxied") {
|
|
734
|
+
if (proxied !== undefined) {
|
|
735
|
+
throw new ValidationError(
|
|
736
|
+
`Provider "${providerId}" auth.proxied is only valid when auth.mode is "oauth2_proxied".`,
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (!proxied || typeof proxied !== "object" || Array.isArray(proxied)) {
|
|
742
|
+
throw new ValidationError(
|
|
743
|
+
`Provider "${providerId}" with auth.mode "oauth2_proxied" must declare auth.proxied.`,
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
const config = Object.fromEntries(Object.entries(proxied));
|
|
747
|
+
for (const key of Object.keys(config)) {
|
|
748
|
+
if (!PROXIED_OAUTH_ALLOWED_FIELDS.has(key)) {
|
|
749
|
+
throw new ValidationError(
|
|
750
|
+
`Provider "${providerId}" has unknown auth.proxied field "${key}".`,
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
for (const field of PROXIED_OAUTH_REQUIRED_FIELDS) {
|
|
755
|
+
if (typeof config[field] !== "string" || !config[field].trim()) {
|
|
756
|
+
throw new ValidationError(
|
|
757
|
+
`Provider "${providerId}" auth.proxied.${field} must be a non-empty string.`,
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
for (const field of ["authorizeUrl", "tokenUrl"] as const) {
|
|
762
|
+
try {
|
|
763
|
+
const endpoint = new URL(String(config[field]));
|
|
764
|
+
if (
|
|
765
|
+
endpoint.protocol !== "https:" ||
|
|
766
|
+
endpoint.username ||
|
|
767
|
+
endpoint.password ||
|
|
768
|
+
endpoint.hash
|
|
769
|
+
) {
|
|
770
|
+
throw new Error("invalid endpoint");
|
|
771
|
+
}
|
|
772
|
+
} catch {
|
|
773
|
+
throw new ValidationError(
|
|
774
|
+
`Provider "${providerId}" auth.proxied.${field} must be an absolute HTTPS URL without credentials or a fragment.`,
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const customScheme = String(config.customScheme);
|
|
779
|
+
if (
|
|
780
|
+
!CUSTOM_SCHEME_REGEX.test(customScheme) ||
|
|
781
|
+
customScheme.toLowerCase().startsWith("http://") ||
|
|
782
|
+
customScheme.toLowerCase().startsWith("https://")
|
|
783
|
+
) {
|
|
784
|
+
throw new ValidationError(
|
|
785
|
+
`Provider "${providerId}" auth.proxied.customScheme must be a non-HTTP custom-scheme URL prefix.`,
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
if (!PROXIED_OAUTH_PROFILE_REGEX.test(String(config.rewriteProfile))) {
|
|
789
|
+
throw new ValidationError(
|
|
790
|
+
`Provider "${providerId}" auth.proxied.rewriteProfile must be a kebab-case profile name.`,
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
if (!PROXIED_OAUTH_ENV_KEY_REGEX.test(String(config.clientIdEnvKey))) {
|
|
794
|
+
throw new ValidationError(
|
|
795
|
+
`Provider "${providerId}" auth.proxied.clientIdEnvKey must be an APIFuse-style uppercase environment key.`,
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
if (config.pkce !== undefined && config.pkce !== "S256" && config.pkce !== "none") {
|
|
799
|
+
throw new ValidationError(
|
|
800
|
+
`Provider "${providerId}" auth.proxied.pkce must be "S256" or "none".`,
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
validateProxiedOAuthParams(config.authorizeParams, "authorizeParams", providerId);
|
|
804
|
+
validateProxiedOAuthParams(config.tokenParams, "tokenParams", providerId);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function validateProviderDeclarationShape(config: unknown): void {
|
|
283
808
|
assertObjectConfig(config);
|
|
284
809
|
assertRequiredField(config, "id");
|
|
285
810
|
assertRequiredField(config, "version", String(config.id));
|
|
286
811
|
assertRequiredField(config, "runtime", String(config.id));
|
|
287
812
|
assertRequiredField(config, "meta", String(config.id));
|
|
288
|
-
assertRequiredField(config, "operations", String(config.id));
|
|
289
813
|
if (typeof config.runtime === "string")
|
|
290
814
|
assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
|
|
815
|
+
if (config.runtimeTarget !== undefined && typeof config.runtimeTarget !== "string") {
|
|
816
|
+
throw new ValidationError(
|
|
817
|
+
`Provider "${String(config.id)}" has invalid runtimeTarget: expected "vanilla" or "engine"`,
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
if (typeof config.runtimeTarget === "string")
|
|
821
|
+
assertLiteralField(
|
|
822
|
+
config.runtimeTarget,
|
|
823
|
+
"runtimeTarget",
|
|
824
|
+
VALID_RUNTIME_TARGETS,
|
|
825
|
+
String(config.id),
|
|
826
|
+
);
|
|
827
|
+
if (config.native !== undefined && config.runtime === "browser") {
|
|
828
|
+
throw new ValidationError(
|
|
829
|
+
`Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`,
|
|
830
|
+
{
|
|
831
|
+
fix: 'Use runtime: "standard" or runtime: "shared", or remove the native declaration.',
|
|
832
|
+
},
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
if (config.native !== undefined && config.runtimeTarget === "vanilla") {
|
|
836
|
+
throw new ValidationError(
|
|
837
|
+
`Provider "${String(config.id)}" cannot declare capability "native" with runtime target "vanilla"; native requires an engine-resident runtime`,
|
|
838
|
+
{
|
|
839
|
+
fix: 'Set runtimeTarget: "engine", or remove the native declaration.',
|
|
840
|
+
},
|
|
841
|
+
);
|
|
842
|
+
}
|
|
291
843
|
const auth = config.auth;
|
|
292
844
|
if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
|
|
293
845
|
assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
|
|
846
|
+
if (auth && typeof auth === "object" && !Array.isArray(auth)) {
|
|
847
|
+
validateProxiedOAuthAuth(Object.fromEntries(Object.entries(auth)), String(config.id));
|
|
848
|
+
}
|
|
294
849
|
if (auth && typeof auth === "object" && "exchange" in auth) {
|
|
295
850
|
throw new ProviderError(
|
|
296
851
|
`Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`,
|
|
@@ -316,6 +871,24 @@ function validateProviderShape(config: unknown): void {
|
|
|
316
871
|
},
|
|
317
872
|
);
|
|
318
873
|
}
|
|
874
|
+
if (
|
|
875
|
+
auth &&
|
|
876
|
+
typeof auth === "object" &&
|
|
877
|
+
"flow" in auth &&
|
|
878
|
+
auth.flow &&
|
|
879
|
+
typeof auth.flow === "object" &&
|
|
880
|
+
"start" in auth.flow &&
|
|
881
|
+
typeof auth.flow.start === "function" &&
|
|
882
|
+
auth.flow.start.length <= 1 &&
|
|
883
|
+
authStartHasHiddenInput(auth.flow.start)
|
|
884
|
+
) {
|
|
885
|
+
throw new ProviderError(
|
|
886
|
+
`Provider "${String(config.id)}" auth.flow.start must not declare an input parameter`,
|
|
887
|
+
{
|
|
888
|
+
fix: "Return a form turn from start(ctx), then receive user input in continue(ctx, input).",
|
|
889
|
+
},
|
|
890
|
+
);
|
|
891
|
+
}
|
|
319
892
|
const access = config.access;
|
|
320
893
|
if (access !== undefined) {
|
|
321
894
|
if (!access || typeof access !== "object" || Array.isArray(access)) {
|
|
@@ -354,11 +927,33 @@ function validateProviderShape(config: unknown): void {
|
|
|
354
927
|
}
|
|
355
928
|
}
|
|
356
929
|
|
|
930
|
+
function validateProviderImplementationShape(config: { id: string }): void {
|
|
931
|
+
const configRecord = config as unknown as Record<string, unknown>;
|
|
932
|
+
assertRequiredField(configRecord, "operations", String(config.id));
|
|
933
|
+
}
|
|
934
|
+
|
|
357
935
|
function validateProviderProxy(config: {
|
|
358
936
|
id: string;
|
|
359
937
|
proxy?: ProviderProxyConfig;
|
|
360
938
|
secrets?: ProviderSecretDeclaration[];
|
|
361
939
|
}): void {
|
|
940
|
+
for (const secret of config.secrets ?? []) {
|
|
941
|
+
if (isEngineOwnedTelemetryEnvName(secret.name)) {
|
|
942
|
+
throw new ValidationError(
|
|
943
|
+
`Provider "${config.id}" cannot declare engine-owned telemetry variable "${secret.name}"`,
|
|
944
|
+
{
|
|
945
|
+
fix: `Remove "${secret.name}" from provider secrets; trace export is configured only on the provider engine.`,
|
|
946
|
+
},
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
if (!isEngineOwnedProxyCredentialName(secret.name)) continue;
|
|
950
|
+
throw new ValidationError(
|
|
951
|
+
`Provider "${config.id}" cannot declare engine-owned proxy credential "${secret.name}"`,
|
|
952
|
+
{
|
|
953
|
+
fix: `Remove "${secret.name}" from provider secrets; configure it only on the provider engine.`,
|
|
954
|
+
},
|
|
955
|
+
);
|
|
956
|
+
}
|
|
362
957
|
const proxy = config.proxy;
|
|
363
958
|
if (proxy === undefined || typeof proxy === "boolean") {
|
|
364
959
|
return;
|
|
@@ -371,11 +966,24 @@ function validateProviderProxy(config: {
|
|
|
371
966
|
},
|
|
372
967
|
);
|
|
373
968
|
}
|
|
374
|
-
rejectUnknownFields(proxy, new Set(["mode", "provider", "geo", "session"]), "proxy");
|
|
969
|
+
rejectUnknownFields(proxy, new Set(["mode", "provider", "providers", "geo", "session"]), "proxy");
|
|
375
970
|
assertLiteralField(proxy.mode, "proxy.mode", VALID_PROVIDER_PROXY_MODES, config.id);
|
|
376
971
|
if (proxy.provider !== undefined) {
|
|
377
972
|
assertLiteralField(proxy.provider, "proxy.provider", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
|
|
378
973
|
}
|
|
974
|
+
if (proxy.providers !== undefined) {
|
|
975
|
+
if (!Array.isArray(proxy.providers) || proxy.providers.length === 0) {
|
|
976
|
+
throw new ValidationError(
|
|
977
|
+
`Provider "${config.id}" has invalid proxy.providers: must be a non-empty array of proxy vendors.`,
|
|
978
|
+
{
|
|
979
|
+
fix: `Use proxy.providers: ["smartproxy", "nodemaven"] to declare an ordered fallback chain.`,
|
|
980
|
+
},
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
for (const vendor of proxy.providers) {
|
|
984
|
+
assertLiteralField(vendor, "proxy.providers[]", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
379
987
|
if (proxy.geo !== undefined) {
|
|
380
988
|
if (!proxy.geo || typeof proxy.geo !== "object" || Array.isArray(proxy.geo)) {
|
|
381
989
|
throw new ValidationError(
|
|
@@ -409,7 +1017,7 @@ function validateProviderProxy(config: {
|
|
|
409
1017
|
}
|
|
410
1018
|
rejectUnknownFields(
|
|
411
1019
|
proxy.session,
|
|
412
|
-
new Set(["affinity", "lifetimeMinutes", "poolSize"]),
|
|
1020
|
+
new Set(["affinity", "lifetimeMinutes", "poolSize", "drainLeadSeconds"]),
|
|
413
1021
|
"proxy.session",
|
|
414
1022
|
);
|
|
415
1023
|
if (proxy.session.affinity !== undefined) {
|
|
@@ -432,20 +1040,63 @@ function validateProviderProxy(config: {
|
|
|
432
1040
|
`Provider "${config.id}" has invalid proxy.session.poolSize: must be a positive integer.`,
|
|
433
1041
|
);
|
|
434
1042
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
(
|
|
439
|
-
)
|
|
440
|
-
|
|
1043
|
+
const drainLeadSeconds = proxy.session.drainLeadSeconds;
|
|
1044
|
+
if (
|
|
1045
|
+
drainLeadSeconds !== undefined &&
|
|
1046
|
+
(!Number.isFinite(drainLeadSeconds) || drainLeadSeconds <= 0)
|
|
1047
|
+
) {
|
|
1048
|
+
throw new ValidationError(
|
|
1049
|
+
`Provider "${config.id}" has invalid proxy.session.drainLeadSeconds: must be a positive number of seconds.`,
|
|
1050
|
+
{
|
|
1051
|
+
fix: `Use proxy.session.drainLeadSeconds: 120 to receive the sticky-expiry drain event 120s before hard expiry.`,
|
|
1052
|
+
},
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
// A drain lead longer than the sticky lifetime would fire the expiring
|
|
1056
|
+
// event before the session is even established, so the provider would
|
|
1057
|
+
// never get a usable window. Reject the contradiction at build time.
|
|
1058
|
+
if (
|
|
1059
|
+
drainLeadSeconds !== undefined &&
|
|
1060
|
+
lifetime !== undefined &&
|
|
1061
|
+
Number.isFinite(lifetime) &&
|
|
1062
|
+
drainLeadSeconds >= lifetime * 60
|
|
1063
|
+
) {
|
|
441
1064
|
throw new ValidationError(
|
|
442
|
-
`Provider "${config.id}"
|
|
1065
|
+
`Provider "${config.id}" has proxy.session.drainLeadSeconds (${drainLeadSeconds}s) greater than or equal to proxy.session.lifetimeMinutes (${lifetime}m).`,
|
|
443
1066
|
{
|
|
444
|
-
fix: `
|
|
1067
|
+
fix: `Lower drainLeadSeconds below the sticky lifetime so the drain event leaves a usable session window.`,
|
|
445
1068
|
},
|
|
446
1069
|
);
|
|
447
1070
|
}
|
|
448
1071
|
}
|
|
1072
|
+
const vendorChain =
|
|
1073
|
+
proxy.providers && proxy.providers.length > 0
|
|
1074
|
+
? proxy.providers
|
|
1075
|
+
: proxy.provider
|
|
1076
|
+
? [proxy.provider]
|
|
1077
|
+
: [];
|
|
1078
|
+
// `decodo`/`custom` are deprecated vendor values (string-union members, so the
|
|
1079
|
+
// @deprecated symbol gate can't catch them — warn at validation time instead).
|
|
1080
|
+
const deprecatedVendors = vendorChain.filter(
|
|
1081
|
+
(vendor) => vendor === "decodo" || vendor === "custom",
|
|
1082
|
+
);
|
|
1083
|
+
if (
|
|
1084
|
+
proxy.mode === "required" &&
|
|
1085
|
+
vendorChain.length > 0 &&
|
|
1086
|
+
deprecatedVendors.length === vendorChain.length
|
|
1087
|
+
) {
|
|
1088
|
+
throw new ValidationError(
|
|
1089
|
+
`Provider "${config.id}" requires proxy egress but declares only deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}.`,
|
|
1090
|
+
{
|
|
1091
|
+
fix: `Use proxy.provider or proxy.providers with "smartproxy" and/or "nodemaven".`,
|
|
1092
|
+
},
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
if (deprecatedVendors.length > 0) {
|
|
1096
|
+
console.warn(
|
|
1097
|
+
`[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven".`,
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
449
1100
|
}
|
|
450
1101
|
|
|
451
1102
|
function validateProviderStt(config: { id: string; stt?: ProviderSttConfig }): void {
|
|
@@ -460,6 +1111,83 @@ function validateProviderStt(config: { id: string; stt?: ProviderSttConfig }): v
|
|
|
460
1111
|
assertLiteralField(stt.mode, "stt.mode", VALID_PROVIDER_STT_MODES, config.id);
|
|
461
1112
|
}
|
|
462
1113
|
|
|
1114
|
+
function validateProviderOcr(config: { id: string; ocr?: ProviderOcrConfig }): void {
|
|
1115
|
+
const ocr = config.ocr;
|
|
1116
|
+
if (ocr === undefined) return;
|
|
1117
|
+
if (!ocr || typeof ocr !== "object" || Array.isArray(ocr)) {
|
|
1118
|
+
throw new ValidationError(`Provider "${config.id}" has invalid ocr: must be an object.`, {
|
|
1119
|
+
fix: `Use ocr: { mode: "required" } or ocr: { mode: "optional" }.`,
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
rejectUnknownFields(ocr, new Set(["mode"]), "ocr");
|
|
1123
|
+
assertLiteralField(ocr.mode, "ocr.mode", VALID_PROVIDER_OCR_MODES, config.id);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function validateProviderResolver(config: { id: string; resolver?: ProviderResolverConfig }): void {
|
|
1127
|
+
const resolver = config.resolver;
|
|
1128
|
+
if (resolver === undefined) return;
|
|
1129
|
+
if (!resolver || typeof resolver !== "object" || Array.isArray(resolver)) {
|
|
1130
|
+
throw new ValidationError(`Provider "${config.id}" has invalid resolver: must be an object.`, {
|
|
1131
|
+
fix: `Set resolver for provider "${config.id}" to { vendors: ["2captcha"], kinds: ["turnstile"] }.`,
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
rejectUnknownFields(
|
|
1135
|
+
resolver,
|
|
1136
|
+
new Set(["vendors", "kinds", "clientProfile"]),
|
|
1137
|
+
"resolver",
|
|
1138
|
+
config.id,
|
|
1139
|
+
);
|
|
1140
|
+
if (resolver.vendors !== undefined) {
|
|
1141
|
+
validateResolverLiteralArray(
|
|
1142
|
+
resolver.vendors,
|
|
1143
|
+
"resolver.vendors",
|
|
1144
|
+
VALID_PROVIDER_RESOLVER_VENDORS,
|
|
1145
|
+
config.id,
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
validateResolverLiteralArray(
|
|
1149
|
+
resolver.kinds,
|
|
1150
|
+
"resolver.kinds",
|
|
1151
|
+
VALID_PROVIDER_CHALLENGE_KINDS,
|
|
1152
|
+
config.id,
|
|
1153
|
+
);
|
|
1154
|
+
if (
|
|
1155
|
+
resolver.clientProfile !== undefined &&
|
|
1156
|
+
(typeof resolver.clientProfile !== "string" || !resolver.clientProfile.trim())
|
|
1157
|
+
) {
|
|
1158
|
+
throw new ValidationError(
|
|
1159
|
+
`Provider "${config.id}" has invalid resolver.clientProfile: must be a non-empty string.`,
|
|
1160
|
+
{
|
|
1161
|
+
fix: `Set resolver.clientProfile for provider "${config.id}" to a transport-owned profile name.`,
|
|
1162
|
+
},
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function validateResolverLiteralArray<TValue extends string>(
|
|
1168
|
+
value: readonly TValue[],
|
|
1169
|
+
field: string,
|
|
1170
|
+
validValues: readonly TValue[],
|
|
1171
|
+
providerId: string,
|
|
1172
|
+
): void {
|
|
1173
|
+
if (!Array.isArray(value)) {
|
|
1174
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an array.`, {
|
|
1175
|
+
fix: `Set ${field} for provider "${providerId}" to an array containing only: ${validValues.join(", ")}.`,
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
for (const [index, item] of value.entries()) {
|
|
1179
|
+
if (typeof item === "string" && validValues.some((validValue) => validValue === item)) {
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
throw new ValidationError(
|
|
1183
|
+
`Provider "${providerId}" has invalid ${field}[${index}]: ${JSON.stringify(item)}. Expected one of: ${validValues.join(", ")}`,
|
|
1184
|
+
{
|
|
1185
|
+
fix: `Set ${field}[${index}] for provider "${providerId}" to one of ${validValues.map((validValue) => `"${validValue}"`).join(", ")}.`,
|
|
1186
|
+
},
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
463
1191
|
function validateOperationIds(
|
|
464
1192
|
providerId: string,
|
|
465
1193
|
operations: Record<string, ProviderOperation>,
|
|
@@ -499,52 +1227,40 @@ function assertNonEmptyString(
|
|
|
499
1227
|
}
|
|
500
1228
|
}
|
|
501
1229
|
|
|
502
|
-
function
|
|
1230
|
+
function validateOperationMetadata(
|
|
503
1231
|
providerId: string,
|
|
504
1232
|
operations: Record<string, ProviderOperation>,
|
|
505
1233
|
): void {
|
|
506
1234
|
for (const [operationName, operation] of Object.entries(operations)) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
},
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
if (toolRouter.name !== undefined && !MCP_TOOL_NAME_REGEX.test(toolRouter.name)) {
|
|
518
|
-
throw new ValidationError(
|
|
519
|
-
`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter.name: expected an MCP-safe name.`,
|
|
520
|
-
{
|
|
521
|
-
fix: `Use letters, numbers, and underscores only, starting with a letter, for example "${providerId.replace(/[^A-Za-z0-9]+/g, "_")}__${operationName.replace(/[^A-Za-z0-9]+/g, "_")}".`,
|
|
522
|
-
},
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
if (toolRouter.riskClass !== undefined) {
|
|
1235
|
+
assertLiteralField(
|
|
1236
|
+
operation.riskClass,
|
|
1237
|
+
`operations.${operationName}.riskClass`,
|
|
1238
|
+
VALID_OPERATION_RISK_CLASSES,
|
|
1239
|
+
providerId,
|
|
1240
|
+
);
|
|
1241
|
+
if (operation.approval !== undefined) {
|
|
526
1242
|
assertLiteralField(
|
|
527
|
-
|
|
528
|
-
`operations.${operationName}.
|
|
529
|
-
|
|
1243
|
+
operation.approval,
|
|
1244
|
+
`operations.${operationName}.approval`,
|
|
1245
|
+
VALID_OPERATION_APPROVAL_POLICIES,
|
|
530
1246
|
providerId,
|
|
531
1247
|
);
|
|
532
1248
|
}
|
|
533
|
-
if (
|
|
1249
|
+
if (operation.connectionMode !== undefined) {
|
|
534
1250
|
assertLiteralField(
|
|
535
|
-
|
|
536
|
-
`operations.${operationName}.
|
|
537
|
-
|
|
1251
|
+
operation.connectionMode,
|
|
1252
|
+
`operations.${operationName}.connectionMode`,
|
|
1253
|
+
VALID_OPERATION_CONNECTION_MODES,
|
|
538
1254
|
providerId,
|
|
539
1255
|
);
|
|
540
1256
|
}
|
|
541
1257
|
if (
|
|
542
|
-
|
|
543
|
-
(typeof
|
|
544
|
-
|
|
1258
|
+
operation.connectionExternalRefParam !== undefined &&
|
|
1259
|
+
(typeof operation.connectionExternalRefParam !== "string" ||
|
|
1260
|
+
operation.connectionExternalRefParam.trim().length === 0)
|
|
545
1261
|
) {
|
|
546
1262
|
throw new ValidationError(
|
|
547
|
-
`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.
|
|
1263
|
+
`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.connectionExternalRefParam: must be a non-empty string.`,
|
|
548
1264
|
{
|
|
549
1265
|
fix: `Use "externalRef" unless the operation has a documented public alias.`,
|
|
550
1266
|
},
|
|
@@ -617,16 +1333,14 @@ function validateOperationContracts(
|
|
|
617
1333
|
}
|
|
618
1334
|
}
|
|
619
1335
|
|
|
620
|
-
function
|
|
1336
|
+
function validateOperationTimeouts(
|
|
621
1337
|
providerId: string,
|
|
622
1338
|
operations: Record<string, ProviderOperation>,
|
|
623
1339
|
): void {
|
|
624
1340
|
for (const [operationName, operation] of Object.entries(operations)) {
|
|
625
|
-
const
|
|
626
|
-
if (!annotations) continue;
|
|
627
|
-
const timeoutMs = annotations.timeoutMs;
|
|
1341
|
+
const timeoutMs = operation.timeoutMs;
|
|
628
1342
|
if (timeoutMs === undefined) continue;
|
|
629
|
-
const field = `operations.${operationName}.
|
|
1343
|
+
const field = `operations.${operationName}.timeoutMs`;
|
|
630
1344
|
if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs))
|
|
631
1345
|
throw new ValidationError(
|
|
632
1346
|
`Provider "${providerId}" has invalid ${field}: must be an integer number of milliseconds.`,
|
|
@@ -702,6 +1416,36 @@ function validateOperationObservability(
|
|
|
702
1416
|
}
|
|
703
1417
|
}
|
|
704
1418
|
|
|
1419
|
+
function validateOperationErrorCodes(
|
|
1420
|
+
providerId: string,
|
|
1421
|
+
operations: Record<string, ProviderOperation>,
|
|
1422
|
+
): void {
|
|
1423
|
+
for (const [operationName, operation] of Object.entries(operations)) {
|
|
1424
|
+
for (const [index, errorCode] of (operation.errorCodes ?? []).entries()) {
|
|
1425
|
+
if (
|
|
1426
|
+
errorCode.status !== undefined &&
|
|
1427
|
+
!VALID_OPERATION_ERROR_STATUSES.some((status) => status === errorCode.status)
|
|
1428
|
+
) {
|
|
1429
|
+
const field = `operations.${operationName}.errorCodes[${index}].status`;
|
|
1430
|
+
throw new ValidationError(
|
|
1431
|
+
`Provider "${providerId}" has invalid ${field}: ${String(errorCode.status)} is not an emittable provider error status.`,
|
|
1432
|
+
{
|
|
1433
|
+
fix: `Set ${field} to one of ${VALID_OPERATION_ERROR_STATUSES.join(", ")}, or omit it.`,
|
|
1434
|
+
},
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
if (
|
|
1438
|
+
errorCode.status !== undefined &&
|
|
1439
|
+
SDK_RUNTIME_OWNED_ERROR_CODES.has(errorCode.code)
|
|
1440
|
+
) {
|
|
1441
|
+
console.warn(
|
|
1442
|
+
`[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.`,
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
705
1449
|
const JSON_TRANSPORT_FIELDS = new Set(["kind"]);
|
|
706
1450
|
const SSE_TRANSPORT_FIELDS = new Set([
|
|
707
1451
|
"kind",
|
|
@@ -972,6 +1716,7 @@ const HEALTH_CHECK_CASE_FIELDS = new Set([
|
|
|
972
1716
|
"input",
|
|
973
1717
|
"prepareInput",
|
|
974
1718
|
"assertions",
|
|
1719
|
+
"scenario",
|
|
975
1720
|
"degradedThresholdMs",
|
|
976
1721
|
"timeoutMs",
|
|
977
1722
|
"expectedStatus",
|
|
@@ -1027,7 +1772,12 @@ function suggestField(unknown: string, candidates: ReadonlySet<string>): string
|
|
|
1027
1772
|
return best;
|
|
1028
1773
|
}
|
|
1029
1774
|
|
|
1030
|
-
function rejectUnknownFields(
|
|
1775
|
+
function rejectUnknownFields(
|
|
1776
|
+
value: object,
|
|
1777
|
+
allowed: ReadonlySet<string>,
|
|
1778
|
+
fieldPath: string,
|
|
1779
|
+
providerId?: string,
|
|
1780
|
+
): void {
|
|
1031
1781
|
for (const key of Object.keys(value)) {
|
|
1032
1782
|
if (allowed.has(key)) continue;
|
|
1033
1783
|
const hint = suggestField(key, allowed);
|
|
@@ -1035,7 +1785,11 @@ function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldP
|
|
|
1035
1785
|
hint
|
|
1036
1786
|
? `Unknown field "${key}" on ${fieldPath}. Did you mean "${hint}"?`
|
|
1037
1787
|
: `Unknown field "${key}" on ${fieldPath}.`,
|
|
1038
|
-
{
|
|
1788
|
+
{
|
|
1789
|
+
fix: providerId
|
|
1790
|
+
? `Remove ${fieldPath}.${key} from provider "${providerId}" or rename it.`
|
|
1791
|
+
: `Remove ${fieldPath}.${key} or rename it.`,
|
|
1792
|
+
},
|
|
1039
1793
|
);
|
|
1040
1794
|
}
|
|
1041
1795
|
}
|
|
@@ -1212,7 +1966,19 @@ function validateHealthCheckCase(
|
|
|
1212
1966
|
throw new ValidationError(
|
|
1213
1967
|
`Provider "${providerId}" ${fieldPath}.name must be a non-empty string.`,
|
|
1214
1968
|
);
|
|
1215
|
-
|
|
1969
|
+
const hasScenario = c.scenario !== undefined;
|
|
1970
|
+
const imperativeFields = [
|
|
1971
|
+
...(c.prepareInput === undefined ? [] : ["prepareInput"]),
|
|
1972
|
+
...(c.assertions === undefined ? [] : ["assertions"]),
|
|
1973
|
+
];
|
|
1974
|
+
if (hasScenario && imperativeFields.length > 0)
|
|
1975
|
+
throw new ValidationError(
|
|
1976
|
+
`Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" cannot declare scenario with ${imperativeFields.join(" and ")}.`,
|
|
1977
|
+
{
|
|
1978
|
+
fix: `Remove ${imperativeFields.map((field) => `${fieldPath}.${field}`).join(" and ")} and keep ${fieldPath}.scenario, or remove ${fieldPath}.scenario to keep the imperative hooks.`,
|
|
1979
|
+
},
|
|
1980
|
+
);
|
|
1981
|
+
if (!hasScenario && typeof c.assertions !== "function")
|
|
1216
1982
|
throw new ValidationError(
|
|
1217
1983
|
`Provider "${providerId}" ${fieldPath}.assertions must be a function.`,
|
|
1218
1984
|
{
|
|
@@ -1223,6 +1989,24 @@ function validateHealthCheckCase(
|
|
|
1223
1989
|
throw new ValidationError(
|
|
1224
1990
|
`Provider "${providerId}" ${fieldPath}.prepareInput must be a function.`,
|
|
1225
1991
|
);
|
|
1992
|
+
if (hasScenario) {
|
|
1993
|
+
const parsedScenario = HealthScenarioSchema.safeParse(c.scenario);
|
|
1994
|
+
if (!parsedScenario.success)
|
|
1995
|
+
throw new ValidationError(
|
|
1996
|
+
`Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" has an invalid scenario: it must conform to HealthScenario.`,
|
|
1997
|
+
{ fix: `Build ${fieldPath}.scenario with defineHealthScenario().` },
|
|
1998
|
+
);
|
|
1999
|
+
const unrelatedOperation = parsedScenario.data.coversOperations.find(
|
|
2000
|
+
(operationId) => operationId !== operationName,
|
|
2001
|
+
);
|
|
2002
|
+
if (unrelatedOperation !== undefined)
|
|
2003
|
+
throw new ValidationError(
|
|
2004
|
+
`Provider "${providerId}" operation "${operationName}" health-check case "${c.name}" scenario.coversOperations cannot claim unrelated operation "${unrelatedOperation}".`,
|
|
2005
|
+
{
|
|
2006
|
+
fix: `Set ${fieldPath}.scenario.coversOperations to ["${operationName}"].`,
|
|
2007
|
+
},
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
1226
2010
|
if (
|
|
1227
2011
|
c.degradedThresholdMs !== undefined &&
|
|
1228
2012
|
(typeof c.degradedThresholdMs !== "number" ||
|
|
@@ -1372,6 +2156,7 @@ const HEALTH_JOURNEY_FIELDS = new Set([
|
|
|
1372
2156
|
"manualTrigger",
|
|
1373
2157
|
"steps",
|
|
1374
2158
|
"run",
|
|
2159
|
+
"scenario",
|
|
1375
2160
|
]);
|
|
1376
2161
|
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set(["kind", "interval", "jitter", "randomize"]);
|
|
1377
2162
|
const HEALTH_JOURNEY_STEP_FIELDS = new Set([
|
|
@@ -1907,6 +2692,14 @@ function validateHealthJourneys(
|
|
|
1907
2692
|
}
|
|
1908
2693
|
if (journey.manualTrigger !== undefined)
|
|
1909
2694
|
validateHealthJourneyManualTrigger(providerId, journey.id, journey.manualTrigger);
|
|
2695
|
+
if (journey.scenario !== undefined && journey.smsMatchers !== undefined)
|
|
2696
|
+
throw new ValidationError(
|
|
2697
|
+
`Provider "${providerId}" healthJourneys.${journey.id}.smsMatchers is not allowed on declarative scenarios.`,
|
|
2698
|
+
);
|
|
2699
|
+
if (journey.scenario !== undefined && journey.requiredSecrets !== undefined)
|
|
2700
|
+
throw new ValidationError(
|
|
2701
|
+
`Provider "${providerId}" healthJourneys.${journey.id}.requiredSecrets is not allowed on declarative scenarios.`,
|
|
2702
|
+
);
|
|
1910
2703
|
if (journey.timeout !== undefined)
|
|
1911
2704
|
assertIsoDuration(
|
|
1912
2705
|
journey.timeout,
|
|
@@ -1977,6 +2770,26 @@ function validateOperationFixtures(
|
|
|
1977
2770
|
fix: `Add operations.${operationName}.handler as an async function with signature (ctx, input) => Promise<output>`,
|
|
1978
2771
|
},
|
|
1979
2772
|
);
|
|
2773
|
+
if (operation.fixtures?.recordedAt !== undefined) {
|
|
2774
|
+
const recordedAt = operation.fixtures.recordedAt;
|
|
2775
|
+
const parsed =
|
|
2776
|
+
typeof recordedAt === "string"
|
|
2777
|
+
? new Date(`${recordedAt}T00:00:00.000Z`)
|
|
2778
|
+
: new Date(Number.NaN);
|
|
2779
|
+
const isCalendarDate =
|
|
2780
|
+
typeof recordedAt === "string" &&
|
|
2781
|
+
/^\d{4}-\d{2}-\d{2}$/.test(recordedAt) &&
|
|
2782
|
+
!Number.isNaN(parsed.getTime()) &&
|
|
2783
|
+
parsed.toISOString().slice(0, 10) === recordedAt;
|
|
2784
|
+
const kstToday = new Date(Date.now() + 9 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
2785
|
+
if (!isCalendarDate || recordedAt > kstToday)
|
|
2786
|
+
throw new ValidationError(
|
|
2787
|
+
`Fixture recordedAt must be a valid, non-future KST calendar date for provider "${providerId}" operation "${operationName}"`,
|
|
2788
|
+
{
|
|
2789
|
+
fix: `Set operations.${operationName}.fixtures.recordedAt to the KST capture date in YYYY-MM-DD format; it must not be in the future.`,
|
|
2790
|
+
},
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
1980
2793
|
if (operation.fixtures?.request !== undefined) {
|
|
1981
2794
|
const result = safeParseSchemaSync(
|
|
1982
2795
|
operation.input,
|
|
@@ -2010,6 +2823,31 @@ function validateOperationFixtures(
|
|
|
2010
2823
|
}
|
|
2011
2824
|
}
|
|
2012
2825
|
|
|
2826
|
+
function resolveOperationFixtureRequests<TOperations extends Record<string, ProviderOperation>>(
|
|
2827
|
+
operations: TOperations,
|
|
2828
|
+
): TOperations {
|
|
2829
|
+
let changed = false;
|
|
2830
|
+
const resolvedOperations = Object.fromEntries(
|
|
2831
|
+
Object.entries(operations).map(([operationName, operation]) => {
|
|
2832
|
+
if (operation.fixtures?.request === undefined) return [operationName, operation];
|
|
2833
|
+
const request = resolveHealthCheckInputDateTokens(operation.fixtures.request);
|
|
2834
|
+
if (request === operation.fixtures.request) return [operationName, operation];
|
|
2835
|
+
changed = true;
|
|
2836
|
+
return [
|
|
2837
|
+
operationName,
|
|
2838
|
+
{
|
|
2839
|
+
...operation,
|
|
2840
|
+
fixtures: {
|
|
2841
|
+
...operation.fixtures,
|
|
2842
|
+
request,
|
|
2843
|
+
},
|
|
2844
|
+
},
|
|
2845
|
+
];
|
|
2846
|
+
}),
|
|
2847
|
+
) as TOperations;
|
|
2848
|
+
return changed ? resolvedOperations : operations;
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2013
2851
|
/**
|
|
2014
2852
|
* Shallow shape guard only: the `deployment` object is passed through
|
|
2015
2853
|
* verbatim and deliberately not deep-validated by the SDK — the APIFuse
|
|
@@ -2023,33 +2861,57 @@ function validateProviderDeployment(providerId: string, deployment: unknown): vo
|
|
|
2023
2861
|
});
|
|
2024
2862
|
}
|
|
2025
2863
|
|
|
2026
|
-
|
|
2864
|
+
/** The second authoring phase for a declaration established by defineProvider. */
|
|
2865
|
+
export type ProviderBuilder<TConfig extends ProviderDeclaration> = <
|
|
2027
2866
|
TOperations extends Record<string, ProviderOperation>,
|
|
2028
|
-
TConfig extends ProviderConfig<TOperations>,
|
|
2029
2867
|
>(
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2868
|
+
implementation: {
|
|
2869
|
+
operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
|
|
2870
|
+
},
|
|
2871
|
+
) => Omit<ProviderDefinition, "operations"> & {
|
|
2872
|
+
operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
|
|
2873
|
+
};
|
|
2874
|
+
|
|
2875
|
+
/** Extract the declaration-derived operation context from a provider builder. */
|
|
2876
|
+
export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer TConfig>
|
|
2877
|
+
? ProviderContext<TConfig>
|
|
2878
|
+
: never;
|
|
2879
|
+
|
|
2880
|
+
/** Annotate an operation while preserving the declaration-derived context. */
|
|
2881
|
+
export type OperationDefinitionFor<
|
|
2882
|
+
TBuilder,
|
|
2883
|
+
TInput extends SchemaLike = SchemaLike,
|
|
2884
|
+
TOutput extends SchemaLike = SchemaLike,
|
|
2885
|
+
> = OperationDefinition<TInput, TOutput, ProviderContextOf<TBuilder>>;
|
|
2886
|
+
|
|
2887
|
+
/** Annotate a built provider while preserving the declaration-derived context. */
|
|
2888
|
+
export type ProviderDefinitionFor<TBuilder> = ProviderDefinition<ProviderContextOf<TBuilder>>;
|
|
2889
|
+
|
|
2890
|
+
/** Establish a provider declaration before its operations are contextually typed. */
|
|
2891
|
+
export function defineProvider<const TConfig extends ProviderDeclaration>(
|
|
2892
|
+
declaration: TConfig &
|
|
2893
|
+
Record<Exclude<keyof TConfig, keyof ProviderDeclaration>, never> &
|
|
2894
|
+
AuthStartNoInputGuard<TConfig>,
|
|
2895
|
+
): ProviderBuilder<TConfig> {
|
|
2896
|
+
validateProviderDeclaration(declaration);
|
|
2897
|
+
const buildProvider = <TOperations extends Record<string, ProviderOperation>>(
|
|
2898
|
+
implementation: {
|
|
2899
|
+
operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
|
|
2900
|
+
},
|
|
2901
|
+
) =>
|
|
2902
|
+
finalizeProvider({
|
|
2903
|
+
...declaration,
|
|
2904
|
+
...implementation,
|
|
2905
|
+
} as ProviderConfig<TOperations, ProviderContext<TConfig>>);
|
|
2906
|
+
return buildProvider as ProviderBuilder<TConfig>;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
function validateProviderDeclaration(config: ProviderDeclaration): void {
|
|
2910
|
+
validateProviderDeclarationShape(config);
|
|
2033
2911
|
if (!CONNECTOR_ID_REGEX.test(config.id))
|
|
2034
2912
|
throw new ProviderError(`Invalid provider id: "${config.id}"`, {
|
|
2035
2913
|
fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
|
|
2036
2914
|
});
|
|
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
2915
|
if (config.healthMonitor !== undefined && config.healthProbe !== undefined)
|
|
2054
2916
|
throw new ValidationError(
|
|
2055
2917
|
`Provider "${config.id}" declares both healthMonitor and healthProbe. They are aliases; declare exactly one.`,
|
|
@@ -2062,10 +2924,18 @@ export function defineProvider<
|
|
|
2062
2924
|
config.healthProbe ?? config.healthMonitor,
|
|
2063
2925
|
config.healthProbe !== undefined ? "healthProbe" : "healthMonitor",
|
|
2064
2926
|
);
|
|
2065
|
-
validateOperationFixtures(config.id, config.operations);
|
|
2066
2927
|
validateProviderDeployment(config.id, config.deployment);
|
|
2928
|
+
try {
|
|
2929
|
+
validateNativeProviderConfig(config.native);
|
|
2930
|
+
} catch (error) {
|
|
2931
|
+
if (error instanceof NativeEgressPolicyValidationError)
|
|
2932
|
+
throw new ValidationError(error.message);
|
|
2933
|
+
throw error;
|
|
2934
|
+
}
|
|
2067
2935
|
validateProviderProxy(config);
|
|
2936
|
+
validateProviderOcr(config);
|
|
2068
2937
|
validateProviderStt(config);
|
|
2938
|
+
validateProviderResolver(config);
|
|
2069
2939
|
if (config.runtime === "browser" && !config.browser)
|
|
2070
2940
|
throw new ProviderError(
|
|
2071
2941
|
`Provider "${config.id}" must define browser.engine when runtime is "browser"`,
|
|
@@ -2078,30 +2948,77 @@ export function defineProvider<
|
|
|
2078
2948
|
`Provider "${config.id}" cannot define browser config unless runtime is "browser"`,
|
|
2079
2949
|
{ fix: 'Set runtime: "browser" or remove the browser config' },
|
|
2080
2950
|
);
|
|
2081
|
-
|
|
2951
|
+
validateFailClosedProviderDeclaration(config);
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
function finalizeProvider<
|
|
2955
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
2956
|
+
TContext,
|
|
2957
|
+
>(
|
|
2958
|
+
config: ProviderConfig<TOperations, TContext>,
|
|
2959
|
+
): Omit<ProviderDefinition, "operations"> & {
|
|
2960
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
2961
|
+
} {
|
|
2962
|
+
validateProviderImplementationShape(config);
|
|
2963
|
+
const operations = resolveOperationFixtureRequests(config.operations);
|
|
2964
|
+
if (Object.keys(config.operations).length === 0)
|
|
2965
|
+
throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
|
|
2966
|
+
fix: "Add at least one operation to the operations object",
|
|
2967
|
+
});
|
|
2968
|
+
validateOperationIds(config.id, config.operations);
|
|
2969
|
+
validateOperationTimeouts(config.id, config.operations);
|
|
2970
|
+
validateOperationObservability(config.id, config.operations);
|
|
2971
|
+
validateOperationErrorCodes(config.id, config.operations);
|
|
2972
|
+
validateOperationTransports(config.id, config.operations);
|
|
2973
|
+
validateOperationContracts(config.id, config.operations);
|
|
2974
|
+
validateOperationMetadata(config.id, config.operations);
|
|
2975
|
+
const journeyCoveredOperations = validateHealthJourneys(
|
|
2976
|
+
config.id,
|
|
2977
|
+
config.operations,
|
|
2978
|
+
config.healthJourneys,
|
|
2979
|
+
);
|
|
2980
|
+
validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
|
|
2981
|
+
validateOperationFixtures(config.id, operations);
|
|
2982
|
+
const provider: Omit<ProviderDefinition, "operations"> & {
|
|
2983
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
2984
|
+
} = {
|
|
2082
2985
|
id: config.id,
|
|
2083
2986
|
version: config.version,
|
|
2084
2987
|
runtime: config.runtime,
|
|
2988
|
+
runtimeTarget: config.runtimeTarget,
|
|
2085
2989
|
// Verbatim passthrough: deployment validation and profile resolution
|
|
2086
2990
|
// are owned by the APIFuse registry builder, not the SDK.
|
|
2087
2991
|
deployment: config.deployment,
|
|
2992
|
+
http: config.http,
|
|
2088
2993
|
allowedHosts: config.allowedHosts,
|
|
2994
|
+
native: config.native,
|
|
2089
2995
|
stealth: config.stealth,
|
|
2090
2996
|
proxy: config.proxy,
|
|
2997
|
+
ocr: config.ocr,
|
|
2091
2998
|
stt: config.stt,
|
|
2999
|
+
resolver: config.resolver,
|
|
2092
3000
|
browser: config.browser,
|
|
2093
3001
|
auth: config.auth,
|
|
3002
|
+
choice: config.choice,
|
|
2094
3003
|
reviewed: config.reviewed,
|
|
2095
3004
|
access: config.access,
|
|
2096
3005
|
secrets: config.secrets,
|
|
3006
|
+
env: config.env,
|
|
2097
3007
|
credential: config.credential,
|
|
2098
3008
|
context: config.context,
|
|
3009
|
+
state: config.state,
|
|
3010
|
+
cache: config.cache,
|
|
3011
|
+
files: config.files,
|
|
2099
3012
|
meta: config.meta,
|
|
2100
|
-
operations
|
|
3013
|
+
operations,
|
|
2101
3014
|
// Transitional healthMonitor → healthProbe alias: mirror whichever field
|
|
2102
3015
|
// was declared onto both so old and new consumers keep working.
|
|
2103
3016
|
healthMonitor: config.healthMonitor ?? config.healthProbe,
|
|
2104
3017
|
healthProbe: config.healthProbe ?? config.healthMonitor,
|
|
2105
3018
|
healthJourneys: config.healthJourneys,
|
|
2106
3019
|
};
|
|
3020
|
+
// Declaration validation never invokes handlers, so their declaration-bound
|
|
3021
|
+
// context parameter is irrelevant to the runtime ProviderDefinition shape.
|
|
3022
|
+
validateFailClosedOperationDeclaration(provider as unknown as ProviderDefinition);
|
|
3023
|
+
return provider;
|
|
2107
3024
|
}
|