@apifuse/provider-sdk 2.2.0-beta.47 → 2.2.0-beta.49
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 +91 -36
- package/CHANGELOG.md +8 -0
- package/README.md +11 -9
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-dev.ts +24 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-pack-smoke.ts +1 -1
- package/bin/apifuse-pack-types.ts +2 -1
- package/bin/apifuse-record.ts +30 -16
- package/bin/apifuse-submit-check.ts +20 -35
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +11 -0
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1178 -0
- package/dist/cli/templates/provider/README.md.tpl +3 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -0
- package/dist/config/loader.d.ts +2 -0
- package/dist/config/loader.js +18 -7
- package/dist/contract-types.d.ts +11 -5
- package/dist/contract.js +21 -10
- package/dist/define.d.ts +25 -22
- package/dist/define.js +49 -75
- 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/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/lint.d.ts +7 -15
- package/dist/lint.js +45 -70
- package/dist/provider.d.ts +3 -1
- package/dist/provider.js +1 -0
- 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 +2 -1
- package/dist/runtime/executor.js +3 -36
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +397 -16
- package/dist/runtime/resolver-public.d.ts +1 -1
- package/dist/runtime/resolver-public.js +1 -1
- package/dist/runtime/resolver-vendors/capsolver.js +9 -3
- package/dist/runtime/resolver-vendors/twocaptcha.js +1 -0
- package/dist/runtime/resolver.d.ts +12 -0
- package/dist/runtime/resolver.js +45 -11
- package/dist/runtime/stealth.d.ts +13 -4
- package/dist/runtime/stealth.js +362 -85
- package/dist/runtime/trace-config.js +2 -1
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +43 -10
- package/dist/server/self-test.d.ts +1 -3
- package/dist/server/self-test.js +2 -12
- package/dist/server/serve-implementation.d.ts +6 -1
- package/dist/server/serve-implementation.js +55 -40
- package/dist/server/trace-output.d.ts +3 -1
- package/dist/server/trace-output.js +61 -2
- package/dist/stealth/profiles.d.ts +9 -8
- package/dist/stealth/profiles.js +123 -286
- package/dist/types.d.ts +116 -108
- package/package.json +2 -1
- 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 +13 -0
- package/src/cli/migrate-operation-declaration.ts +1654 -0
- package/src/cli/templates/provider/README.md.tpl +3 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +2 -0
- package/src/config/loader.ts +31 -6
- package/src/contract-types.ts +11 -5
- package/src/contract.ts +21 -10
- package/src/define.ts +107 -119
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- package/src/index.ts +13 -5
- package/src/lint.ts +58 -92
- package/src/provider.ts +25 -3
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +7 -40
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/otlp.ts +467 -21
- package/src/runtime/resolver-public.ts +3 -0
- package/src/runtime/resolver-vendors/capsolver.ts +12 -4
- package/src/runtime/resolver-vendors/twocaptcha.ts +1 -0
- package/src/runtime/resolver.ts +68 -19
- package/src/runtime/stealth.ts +435 -103
- package/src/runtime/trace-config.ts +3 -2
- package/src/runtime/trace.ts +57 -17
- package/src/server/self-test.ts +2 -9
- package/src/server/serve-implementation.ts +89 -72
- package/src/server/trace-output.ts +99 -2
- package/src/stealth/profiles.ts +169 -327
- package/src/types.ts +114 -137
|
@@ -113,9 +113,9 @@ Structured errors return an `error` object with `code`, `message`,
|
|
|
113
113
|
`connection.secrets`, and read them with `ctx.credential`.
|
|
114
114
|
- Auth flow: call `/auth/start`, then `/auth/continue` with the same `flowId`;
|
|
115
115
|
carry returned `contextPatch` values into the next request's `context`.
|
|
116
|
-
- Stealth/browser runtime: keep access-sensitive operations on `ctx.stealth.fetch()` with
|
|
117
|
-
SDK stealth `
|
|
118
|
-
and supports Chrome, Firefox, and Safari
|
|
116
|
+
- Stealth/browser runtime: keep access-sensitive operations on `ctx.stealth.fetch()` with
|
|
117
|
+
structured SDK `stealth: { browser, os }` selection; the TypeScript stealth runtime uses
|
|
118
|
+
`wreq-js` internally and supports Chrome, Firefox, and Safari. Use `ctx.browser` only when
|
|
119
119
|
the provider needs browser execution; TypeScript browser Providers use
|
|
120
120
|
`browser.engine: "playwright-stealth"` (`nodriver` is Python-runtime only). Install local Chromium with
|
|
121
121
|
`bunx playwright install chromium` or set `APIFUSE__CDP_POOL__URL`.
|
|
@@ -4,6 +4,8 @@ import type { ProviderContext } from "../index";
|
|
|
4
4
|
import { pingInputSchema, pingOutputSchema } from "../schemas/ping";
|
|
5
5
|
|
|
6
6
|
export const pingOperation = defineOperation<ProviderContext>()({
|
|
7
|
+
connectionMode: "none",
|
|
8
|
+
riskClass: "read",
|
|
7
9
|
descriptionKey: "operations.ping.description",
|
|
8
10
|
input: pingInputSchema,
|
|
9
11
|
output: pingOutputSchema,
|
package/src/config/loader.ts
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
NODEMAVEN_PASSWORD_ENV,
|
|
14
14
|
NODEMAVEN_USERNAME_ENV,
|
|
15
15
|
type ProxyProtocol,
|
|
16
|
-
hasNodemavenCredentials,
|
|
17
16
|
nodemavenPoolSize,
|
|
18
17
|
synthesizeNodemavenProxy,
|
|
19
18
|
} from "../runtime/proxy-nodemaven.js";
|
|
@@ -83,6 +82,8 @@ export type ProxyResolutionOptions = {
|
|
|
83
82
|
*/
|
|
84
83
|
proxyRefreshEpoch?: number;
|
|
85
84
|
telemetry?: ProxyTelemetrySink;
|
|
85
|
+
/** Engine-owned credential snapshot. Provider code must never populate this field. */
|
|
86
|
+
engineCredentials?: Readonly<Record<string, string>>;
|
|
86
87
|
};
|
|
87
88
|
|
|
88
89
|
export type ProxyCacheStatus =
|
|
@@ -527,7 +528,7 @@ export async function resolveProxyConfigAsync(
|
|
|
527
528
|
const poolIndex = vendorIndex === startVendorIndex ? startPoolIndex : 0;
|
|
528
529
|
const protocol = options.protocol ?? VENDOR_DEFAULT_PROTOCOL[vendor];
|
|
529
530
|
|
|
530
|
-
if (!vendorHasCredentials(vendor)) {
|
|
531
|
+
if (!vendorHasCredentials(vendor, options.engineCredentials)) {
|
|
531
532
|
options.telemetry?.recordProxyVendorFailover?.({
|
|
532
533
|
vendor,
|
|
533
534
|
nextVendor,
|
|
@@ -555,6 +556,7 @@ export async function resolveProxyConfigAsync(
|
|
|
555
556
|
protocol,
|
|
556
557
|
poolIndex,
|
|
557
558
|
refreshEpoch,
|
|
559
|
+
credentials: options.engineCredentials,
|
|
558
560
|
});
|
|
559
561
|
} catch (error) {
|
|
560
562
|
// Config/programming errors (invalid filter, etc.) are not vendor
|
|
@@ -805,9 +807,28 @@ function envDefaultProvider(): ProviderProxyProvider | undefined {
|
|
|
805
807
|
return (raw as ProviderProxyProvider | undefined) ?? undefined;
|
|
806
808
|
}
|
|
807
809
|
|
|
808
|
-
function vendorHasCredentials(
|
|
809
|
-
|
|
810
|
-
|
|
810
|
+
function vendorHasCredentials(
|
|
811
|
+
vendor: ProxyVendorName,
|
|
812
|
+
credentials?: Readonly<Record<string, string>>,
|
|
813
|
+
): boolean {
|
|
814
|
+
if (vendor === "nodemaven") {
|
|
815
|
+
return Boolean(
|
|
816
|
+
(credentials === undefined
|
|
817
|
+
? process.env[NODEMAVEN_USERNAME_ENV]
|
|
818
|
+
: credentials[NODEMAVEN_USERNAME_ENV]
|
|
819
|
+
)?.trim() &&
|
|
820
|
+
(credentials === undefined
|
|
821
|
+
? process.env[NODEMAVEN_PASSWORD_ENV]
|
|
822
|
+
: credentials[NODEMAVEN_PASSWORD_ENV]
|
|
823
|
+
)?.trim(),
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
return Boolean(
|
|
827
|
+
(credentials === undefined
|
|
828
|
+
? process.env[SMARTPROXY_APP_KEY_ENV]
|
|
829
|
+
: credentials[SMARTPROXY_APP_KEY_ENV]
|
|
830
|
+
)?.trim(),
|
|
831
|
+
);
|
|
811
832
|
}
|
|
812
833
|
|
|
813
834
|
function missingCredentialEnv(vendor: ProxyVendorName): string {
|
|
@@ -1710,7 +1731,11 @@ function markSmartproxyCacheInvalidated(options: ProxyResolutionOptions = {}): s
|
|
|
1710
1731
|
}
|
|
1711
1732
|
|
|
1712
1733
|
const lifetimeMinutes = resolveSmartproxyLifetime(policy);
|
|
1713
|
-
const appKey =
|
|
1734
|
+
const appKey = (
|
|
1735
|
+
options.engineCredentials === undefined
|
|
1736
|
+
? process.env[SMARTPROXY_APP_KEY_ENV]
|
|
1737
|
+
: options.engineCredentials[SMARTPROXY_APP_KEY_ENV]
|
|
1738
|
+
)?.trim();
|
|
1714
1739
|
if (!appKey) return undefined;
|
|
1715
1740
|
const cacheKey = buildSmartproxyCacheKey(
|
|
1716
1741
|
policy,
|
package/src/contract-types.ts
CHANGED
|
@@ -30,17 +30,23 @@ export interface ProviderContractSnapshot {
|
|
|
30
30
|
|
|
31
31
|
export interface ProviderContractOperation {
|
|
32
32
|
readonly id: string;
|
|
33
|
+
readonly connectionMode?: JsonValue;
|
|
34
|
+
readonly connectionExternalRefParam?: JsonValue;
|
|
35
|
+
readonly riskClass: JsonValue;
|
|
36
|
+
readonly approval?: JsonValue;
|
|
37
|
+
readonly timeoutMs?: JsonValue;
|
|
38
|
+
readonly titleKey?: JsonValue;
|
|
33
39
|
readonly descriptionKey?: JsonValue;
|
|
34
|
-
readonly
|
|
40
|
+
readonly summaryKey?: JsonValue;
|
|
41
|
+
readonly markdownKey?: JsonValue;
|
|
35
42
|
readonly whenToUseKeys?: JsonValue;
|
|
36
43
|
readonly whenNotToUseKeys?: JsonValue;
|
|
37
|
-
readonly
|
|
38
|
-
readonly
|
|
39
|
-
readonly
|
|
44
|
+
readonly normalizationNotesKeys?: JsonValue;
|
|
45
|
+
readonly errorCodes?: JsonValue;
|
|
46
|
+
readonly examples?: JsonValue;
|
|
40
47
|
readonly contract?: JsonValue;
|
|
41
48
|
readonly tags?: JsonValue;
|
|
42
49
|
readonly relatedOperations?: JsonValue;
|
|
43
|
-
readonly toolRouter?: JsonValue;
|
|
44
50
|
readonly observability?: JsonValue;
|
|
45
51
|
readonly transport?: JsonValue;
|
|
46
52
|
readonly inputSchema: JsonValue;
|
package/src/contract.ts
CHANGED
|
@@ -82,17 +82,22 @@ function extractOperation(
|
|
|
82
82
|
operationId: string,
|
|
83
83
|
operation: OperationDefinition,
|
|
84
84
|
): ProviderContractOperation {
|
|
85
|
+
const connectionMode = toJsonValue(operation.connectionMode);
|
|
86
|
+
const connectionExternalRefParam = toJsonValue(operation.connectionExternalRefParam);
|
|
87
|
+
const approval = toJsonValue(operation.approval);
|
|
88
|
+
const timeoutMs = toJsonValue(operation.timeoutMs);
|
|
89
|
+
const titleKey = toJsonValue(operation.titleKey);
|
|
85
90
|
const descriptionKey = toJsonValue(operation.descriptionKey);
|
|
86
|
-
const
|
|
91
|
+
const summaryKey = toJsonValue(operation.summaryKey);
|
|
92
|
+
const markdownKey = toJsonValue(operation.markdownKey);
|
|
87
93
|
const whenToUseKeys = toJsonValue(operation.whenToUseKeys);
|
|
88
94
|
const whenNotToUseKeys = toJsonValue(operation.whenNotToUseKeys);
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
95
|
+
const normalizationNotesKeys = toJsonValue(operation.normalizationNotesKeys);
|
|
96
|
+
const errorCodes = toJsonValue(operation.errorCodes);
|
|
97
|
+
const examples = toJsonValue(operation.examples);
|
|
92
98
|
const contract = toJsonValue(operation.contract);
|
|
93
99
|
const tags = toJsonValue(operation.tags);
|
|
94
100
|
const relatedOperations = toJsonValue(operation.relatedOperations);
|
|
95
|
-
const toolRouter = toJsonValue(operation.toolRouter);
|
|
96
101
|
const observability = toJsonValue(operation.observability);
|
|
97
102
|
const transport = extractTransport(operation.transport);
|
|
98
103
|
const fixtures = toJsonValue(operation.fixtures);
|
|
@@ -105,17 +110,23 @@ function extractOperation(
|
|
|
105
110
|
id: operationId,
|
|
106
111
|
inputSchema: describeSchema(operation.input),
|
|
107
112
|
outputSchema: describeSchema(operation.output),
|
|
113
|
+
...(connectionMode === undefined ? {} : { connectionMode }),
|
|
114
|
+
...(connectionExternalRefParam === undefined ? {} : { connectionExternalRefParam }),
|
|
115
|
+
riskClass: operation.riskClass,
|
|
116
|
+
...(approval === undefined ? {} : { approval }),
|
|
117
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
118
|
+
...(titleKey === undefined ? {} : { titleKey }),
|
|
108
119
|
...(descriptionKey === undefined ? {} : { descriptionKey }),
|
|
109
|
-
...(
|
|
120
|
+
...(summaryKey === undefined ? {} : { summaryKey }),
|
|
121
|
+
...(markdownKey === undefined ? {} : { markdownKey }),
|
|
110
122
|
...(whenToUseKeys === undefined ? {} : { whenToUseKeys }),
|
|
111
123
|
...(whenNotToUseKeys === undefined ? {} : { whenNotToUseKeys }),
|
|
112
|
-
...(
|
|
113
|
-
...(
|
|
114
|
-
...(
|
|
124
|
+
...(normalizationNotesKeys === undefined ? {} : { normalizationNotesKeys }),
|
|
125
|
+
...(errorCodes === undefined ? {} : { errorCodes }),
|
|
126
|
+
...(examples === undefined ? {} : { examples }),
|
|
115
127
|
...(contract === undefined ? {} : { contract }),
|
|
116
128
|
...(tags === undefined ? {} : { tags }),
|
|
117
129
|
...(relatedOperations === undefined ? {} : { relatedOperations }),
|
|
118
|
-
...(toolRouter === undefined ? {} : { toolRouter }),
|
|
119
130
|
...(observability === undefined ? {} : { observability }),
|
|
120
131
|
...(transport === undefined ? {} : { transport }),
|
|
121
132
|
...(fixtures === undefined ? {} : { fixtures }),
|
package/src/define.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "./declaration-validation.js";
|
|
7
7
|
import { SDK_RUNTIME_OWNED_ERROR_CODES } from "./error-resolution.js";
|
|
8
8
|
import { ProviderError, ValidationError } from "./errors.js";
|
|
9
|
+
import { isEngineOwnedProxyCredentialName, isEngineOwnedTelemetryEnvName } from "./engine.js";
|
|
9
10
|
import { HealthScenarioSchema } from "./health-scenario.js";
|
|
10
11
|
import {
|
|
11
12
|
NativeEgressPolicyValidationError,
|
|
@@ -35,23 +36,22 @@ import type {
|
|
|
35
36
|
ProviderAccessConfig,
|
|
36
37
|
ProviderChallengeKind,
|
|
37
38
|
ProviderContext,
|
|
38
|
-
ProviderContextFor,
|
|
39
39
|
ProviderDefinition,
|
|
40
40
|
ProviderDeploymentOverrides,
|
|
41
41
|
ProviderHealthMonitorConfig,
|
|
42
42
|
ProviderOcrConfig,
|
|
43
43
|
ProviderProxyConfig,
|
|
44
|
-
ProviderProxyProvider,
|
|
45
44
|
ProviderPublicProfile,
|
|
46
45
|
ProviderResolverConfig,
|
|
47
46
|
ProviderResolverVendor,
|
|
48
47
|
ProviderReviewed,
|
|
48
|
+
ProviderRuntimeTarget,
|
|
49
49
|
ProviderSecretDeclaration,
|
|
50
50
|
ProviderStreamEvent,
|
|
51
51
|
ProviderSttConfig,
|
|
52
52
|
SchemaLike,
|
|
53
53
|
SmsOtpMatcherDefinition,
|
|
54
|
-
|
|
54
|
+
StealthProfileSelection,
|
|
55
55
|
} from "./types.js";
|
|
56
56
|
import {
|
|
57
57
|
HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX,
|
|
@@ -95,6 +95,7 @@ interface ProviderImplementationProfile {
|
|
|
95
95
|
const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
|
|
96
96
|
const OPERATION_ID_REGEX = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
97
97
|
const VALID_RUNTIMES = ["standard", "shared", "browser"] as const;
|
|
98
|
+
const VALID_RUNTIME_TARGETS = ["vanilla", "engine"] as const;
|
|
98
99
|
const VALID_AUTH_MODES = [
|
|
99
100
|
"none",
|
|
100
101
|
"platform-managed",
|
|
@@ -162,24 +163,10 @@ export const VALID_PROVIDER_CHALLENGE_KINDS = exhaustiveLiteralArray<ProviderCha
|
|
|
162
163
|
"akamai_sec_cpt",
|
|
163
164
|
"akamai_sensor",
|
|
164
165
|
] as const);
|
|
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
|
-
};
|
|
179
166
|
const RESERVED_OPERATION_IDS = new Set(["auth", "health"]);
|
|
180
|
-
const MCP_TOOL_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_]{0,127}$/;
|
|
181
167
|
const VALID_OPERATION_RISK_CLASSES = ["read", "write", "destructive", "external-send"] as const;
|
|
182
168
|
const VALID_OPERATION_APPROVAL_POLICIES = ["never", "risk-based", "always"] as const;
|
|
169
|
+
const VALID_OPERATION_CONNECTION_MODES = ["none", "optional", "required"] as const;
|
|
183
170
|
const VALID_OPERATION_TRANSPORT_KINDS = ["json", "sse", "http-stream", "websocket"] as const;
|
|
184
171
|
const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
|
|
185
172
|
const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
@@ -583,6 +570,8 @@ export interface ProviderDeclaration {
|
|
|
583
570
|
id: string;
|
|
584
571
|
version: string;
|
|
585
572
|
runtime: "standard" | "shared" | "browser";
|
|
573
|
+
/** Provider business-logic target. Omit only while migrating a legacy provider. */
|
|
574
|
+
runtimeTarget?: ProviderRuntimeTarget;
|
|
586
575
|
/**
|
|
587
576
|
* Optional deployment overrides, passed through verbatim onto the returned
|
|
588
577
|
* provider definition. The SDK types this field but does not deep-validate
|
|
@@ -590,35 +579,36 @@ export interface ProviderDeclaration {
|
|
|
590
579
|
* resolves omitted fields against the runtime deployment profiles.
|
|
591
580
|
*/
|
|
592
581
|
deployment?: ProviderDeploymentOverrides;
|
|
593
|
-
/** Declares
|
|
594
|
-
http?: true;
|
|
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. */
|
|
595
585
|
allowedHosts?: string[];
|
|
596
586
|
native?: NativeProviderConfig;
|
|
597
|
-
stealth?:
|
|
598
|
-
|
|
599
|
-
platform: StealthPlatform;
|
|
600
|
-
};
|
|
587
|
+
stealth?: StealthProfileSelection;
|
|
588
|
+
/** Declares proxy policy; this is provider intent and does not add a `ctx.proxy` member. */
|
|
601
589
|
proxy?: ProviderProxyConfig;
|
|
602
590
|
ocr?: ProviderOcrConfig;
|
|
603
591
|
stt?: ProviderSttConfig;
|
|
604
592
|
resolver?: ProviderResolverConfig;
|
|
605
593
|
browser?: { engine: BrowserEngine };
|
|
606
594
|
auth?: AuthConfig;
|
|
607
|
-
/** Declares
|
|
608
|
-
choice?: true;
|
|
595
|
+
/** Declares the choice capability binding. A bare object states use without configuration. */
|
|
596
|
+
choice?: Record<string, never> | true;
|
|
609
597
|
reviewed?: ProviderReviewed;
|
|
610
598
|
access?: ProviderAccessConfig;
|
|
599
|
+
/** Declares secret requirements; this does not add a `ctx.secrets` member. */
|
|
611
600
|
secrets?: ProviderSecretDeclaration[];
|
|
612
|
-
/** Declares
|
|
613
|
-
env?: true;
|
|
601
|
+
/** Declares the environment capability binding. A bare object states use without configuration. */
|
|
602
|
+
env?: Record<string, never> | true;
|
|
614
603
|
credential?: CredentialDeclaration;
|
|
604
|
+
/** Declares provider context metadata; this does not add a `ctx.context` member. */
|
|
615
605
|
context?: ContextDeclaration;
|
|
616
|
-
/** Declares
|
|
617
|
-
state?: true;
|
|
618
|
-
/** Declares
|
|
619
|
-
cache?: true;
|
|
620
|
-
/** Declares
|
|
621
|
-
files?: true;
|
|
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;
|
|
622
612
|
meta: {
|
|
623
613
|
displayName: string;
|
|
624
614
|
displayNameKey?: string;
|
|
@@ -822,6 +812,18 @@ function validateProviderDeclarationShape(config: unknown): void {
|
|
|
822
812
|
assertRequiredField(config, "meta", String(config.id));
|
|
823
813
|
if (typeof config.runtime === "string")
|
|
824
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
|
+
);
|
|
825
827
|
if (config.native !== undefined && config.runtime === "browser") {
|
|
826
828
|
throw new ValidationError(
|
|
827
829
|
`Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`,
|
|
@@ -830,6 +832,14 @@ function validateProviderDeclarationShape(config: unknown): void {
|
|
|
830
832
|
},
|
|
831
833
|
);
|
|
832
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
|
+
}
|
|
833
843
|
const auth = config.auth;
|
|
834
844
|
if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
|
|
835
845
|
assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
|
|
@@ -927,6 +937,23 @@ function validateProviderProxy(config: {
|
|
|
927
937
|
proxy?: ProviderProxyConfig;
|
|
928
938
|
secrets?: ProviderSecretDeclaration[];
|
|
929
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
|
+
}
|
|
930
957
|
const proxy = config.proxy;
|
|
931
958
|
if (proxy === undefined || typeof proxy === "boolean") {
|
|
932
959
|
return;
|
|
@@ -1042,44 +1069,12 @@ function validateProviderProxy(config: {
|
|
|
1042
1069
|
);
|
|
1043
1070
|
}
|
|
1044
1071
|
}
|
|
1045
|
-
// Every credentialed vendor in a required-mode chain must declare its
|
|
1046
|
-
// provider secret(s) so a missing credential fails at build/validation time,
|
|
1047
|
-
// not during a live outage. This covers the fallback legs too (not just the
|
|
1048
|
-
// first vendor): a declared-but-uncredentialed nodemaven fallback would leave
|
|
1049
|
-
// the chain silently down to a single vendor, reintroducing the SPOF the chain
|
|
1050
|
-
// removes.
|
|
1051
1072
|
const vendorChain =
|
|
1052
1073
|
proxy.providers && proxy.providers.length > 0
|
|
1053
1074
|
? proxy.providers
|
|
1054
1075
|
: proxy.provider
|
|
1055
1076
|
? [proxy.provider]
|
|
1056
1077
|
: [];
|
|
1057
|
-
if (proxy.mode === "required") {
|
|
1058
|
-
for (const vendor of vendorChain) {
|
|
1059
|
-
const requiredSecrets = VENDOR_REQUIRED_SECRETS[vendor];
|
|
1060
|
-
if (!requiredSecrets) continue;
|
|
1061
|
-
for (const secretName of requiredSecrets) {
|
|
1062
|
-
// Match the canonical runtime gate (assertRequiredSecretsPresent /
|
|
1063
|
-
// listMissingRequiredSecrets), which enforces only `required === true`
|
|
1064
|
-
// declarations. A declaration that omits `required` (defaulting to
|
|
1065
|
-
// optional) is skipped at runtime, so accepting it here would pass
|
|
1066
|
-
// validation while leaving the credential unenforced until proxy
|
|
1067
|
-
// resolution during a live request — the fail-open gap this check exists
|
|
1068
|
-
// to close.
|
|
1069
|
-
const declared = config.secrets?.some(
|
|
1070
|
-
(secret) => secret.name === secretName && secret.required === true,
|
|
1071
|
-
);
|
|
1072
|
-
if (!declared) {
|
|
1073
|
-
throw new ValidationError(
|
|
1074
|
-
`Provider "${config.id}" requires ${vendor} egress but does not declare ${secretName}.`,
|
|
1075
|
-
{
|
|
1076
|
-
fix: `Add secrets: [{ name: "${secretName}", required: true }] to the provider (every vendor in a required proxy chain must declare its credential secrets).`,
|
|
1077
|
-
},
|
|
1078
|
-
);
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
1078
|
// `decodo`/`custom` are deprecated vendor values (string-union members, so the
|
|
1084
1079
|
// @deprecated symbol gate can't catch them — warn at validation time instead).
|
|
1085
1080
|
const deprecatedVendors = vendorChain.filter(
|
|
@@ -1232,52 +1227,40 @@ function assertNonEmptyString(
|
|
|
1232
1227
|
}
|
|
1233
1228
|
}
|
|
1234
1229
|
|
|
1235
|
-
function
|
|
1230
|
+
function validateOperationMetadata(
|
|
1236
1231
|
providerId: string,
|
|
1237
1232
|
operations: Record<string, ProviderOperation>,
|
|
1238
1233
|
): void {
|
|
1239
1234
|
for (const [operationName, operation] of Object.entries(operations)) {
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
},
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
if (toolRouter.name !== undefined && !MCP_TOOL_NAME_REGEX.test(toolRouter.name)) {
|
|
1251
|
-
throw new ValidationError(
|
|
1252
|
-
`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter.name: expected an MCP-safe name.`,
|
|
1253
|
-
{
|
|
1254
|
-
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, "_")}".`,
|
|
1255
|
-
},
|
|
1256
|
-
);
|
|
1257
|
-
}
|
|
1258
|
-
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) {
|
|
1259
1242
|
assertLiteralField(
|
|
1260
|
-
|
|
1261
|
-
`operations.${operationName}.
|
|
1262
|
-
|
|
1243
|
+
operation.approval,
|
|
1244
|
+
`operations.${operationName}.approval`,
|
|
1245
|
+
VALID_OPERATION_APPROVAL_POLICIES,
|
|
1263
1246
|
providerId,
|
|
1264
1247
|
);
|
|
1265
1248
|
}
|
|
1266
|
-
if (
|
|
1249
|
+
if (operation.connectionMode !== undefined) {
|
|
1267
1250
|
assertLiteralField(
|
|
1268
|
-
|
|
1269
|
-
`operations.${operationName}.
|
|
1270
|
-
|
|
1251
|
+
operation.connectionMode,
|
|
1252
|
+
`operations.${operationName}.connectionMode`,
|
|
1253
|
+
VALID_OPERATION_CONNECTION_MODES,
|
|
1271
1254
|
providerId,
|
|
1272
1255
|
);
|
|
1273
1256
|
}
|
|
1274
1257
|
if (
|
|
1275
|
-
|
|
1276
|
-
(typeof
|
|
1277
|
-
|
|
1258
|
+
operation.connectionExternalRefParam !== undefined &&
|
|
1259
|
+
(typeof operation.connectionExternalRefParam !== "string" ||
|
|
1260
|
+
operation.connectionExternalRefParam.trim().length === 0)
|
|
1278
1261
|
) {
|
|
1279
1262
|
throw new ValidationError(
|
|
1280
|
-
`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.
|
|
1263
|
+
`Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.connectionExternalRefParam: must be a non-empty string.`,
|
|
1281
1264
|
{
|
|
1282
1265
|
fix: `Use "externalRef" unless the operation has a documented public alias.`,
|
|
1283
1266
|
},
|
|
@@ -1350,16 +1333,14 @@ function validateOperationContracts(
|
|
|
1350
1333
|
}
|
|
1351
1334
|
}
|
|
1352
1335
|
|
|
1353
|
-
function
|
|
1336
|
+
function validateOperationTimeouts(
|
|
1354
1337
|
providerId: string,
|
|
1355
1338
|
operations: Record<string, ProviderOperation>,
|
|
1356
1339
|
): void {
|
|
1357
1340
|
for (const [operationName, operation] of Object.entries(operations)) {
|
|
1358
|
-
const
|
|
1359
|
-
if (!annotations) continue;
|
|
1360
|
-
const timeoutMs = annotations.timeoutMs;
|
|
1341
|
+
const timeoutMs = operation.timeoutMs;
|
|
1361
1342
|
if (timeoutMs === undefined) continue;
|
|
1362
|
-
const field = `operations.${operationName}.
|
|
1343
|
+
const field = `operations.${operationName}.timeoutMs`;
|
|
1363
1344
|
if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs))
|
|
1364
1345
|
throw new ValidationError(
|
|
1365
1346
|
`Provider "${providerId}" has invalid ${field}: must be an integer number of milliseconds.`,
|
|
@@ -1440,12 +1421,12 @@ function validateOperationErrorCodes(
|
|
|
1440
1421
|
operations: Record<string, ProviderOperation>,
|
|
1441
1422
|
): void {
|
|
1442
1423
|
for (const [operationName, operation] of Object.entries(operations)) {
|
|
1443
|
-
for (const [index, errorCode] of (operation.
|
|
1424
|
+
for (const [index, errorCode] of (operation.errorCodes ?? []).entries()) {
|
|
1444
1425
|
if (
|
|
1445
1426
|
errorCode.status !== undefined &&
|
|
1446
1427
|
!VALID_OPERATION_ERROR_STATUSES.some((status) => status === errorCode.status)
|
|
1447
1428
|
) {
|
|
1448
|
-
const field = `operations.${operationName}.
|
|
1429
|
+
const field = `operations.${operationName}.errorCodes[${index}].status`;
|
|
1449
1430
|
throw new ValidationError(
|
|
1450
1431
|
`Provider "${providerId}" has invalid ${field}: ${String(errorCode.status)} is not an emittable provider error status.`,
|
|
1451
1432
|
{
|
|
@@ -2881,19 +2862,19 @@ function validateProviderDeployment(providerId: string, deployment: unknown): vo
|
|
|
2881
2862
|
}
|
|
2882
2863
|
|
|
2883
2864
|
/** The second authoring phase for a declaration established by defineProvider. */
|
|
2884
|
-
export type ProviderBuilder<
|
|
2865
|
+
export type ProviderBuilder<TConfig extends ProviderDeclaration> = <
|
|
2885
2866
|
TOperations extends Record<string, ProviderOperation>,
|
|
2886
2867
|
>(
|
|
2887
2868
|
implementation: {
|
|
2888
|
-
operations: OperationMapConfig<TOperations,
|
|
2869
|
+
operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
|
|
2889
2870
|
},
|
|
2890
2871
|
) => Omit<ProviderDefinition, "operations"> & {
|
|
2891
|
-
operations: OperationMapConfig<TOperations,
|
|
2872
|
+
operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
|
|
2892
2873
|
};
|
|
2893
2874
|
|
|
2894
2875
|
/** Extract the declaration-derived operation context from a provider builder. */
|
|
2895
|
-
export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer
|
|
2896
|
-
?
|
|
2876
|
+
export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer TConfig>
|
|
2877
|
+
? ProviderContext<TConfig>
|
|
2897
2878
|
: never;
|
|
2898
2879
|
|
|
2899
2880
|
/** Annotate an operation while preserving the declaration-derived context. */
|
|
@@ -2907,22 +2888,22 @@ export type OperationDefinitionFor<
|
|
|
2907
2888
|
export type ProviderDefinitionFor<TBuilder> = ProviderDefinition<ProviderContextOf<TBuilder>>;
|
|
2908
2889
|
|
|
2909
2890
|
/** Establish a provider declaration before its operations are contextually typed. */
|
|
2910
|
-
export function defineProvider<const
|
|
2911
|
-
declaration:
|
|
2912
|
-
Record<Exclude<keyof
|
|
2913
|
-
AuthStartNoInputGuard<
|
|
2914
|
-
): ProviderBuilder<
|
|
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> {
|
|
2915
2896
|
validateProviderDeclaration(declaration);
|
|
2916
2897
|
const buildProvider = <TOperations extends Record<string, ProviderOperation>>(
|
|
2917
2898
|
implementation: {
|
|
2918
|
-
operations: OperationMapConfig<TOperations,
|
|
2899
|
+
operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
|
|
2919
2900
|
},
|
|
2920
2901
|
) =>
|
|
2921
2902
|
finalizeProvider({
|
|
2922
2903
|
...declaration,
|
|
2923
2904
|
...implementation,
|
|
2924
|
-
} as ProviderConfig<TOperations,
|
|
2925
|
-
return buildProvider as ProviderBuilder<
|
|
2905
|
+
} as ProviderConfig<TOperations, ProviderContext<TConfig>>);
|
|
2906
|
+
return buildProvider as ProviderBuilder<TConfig>;
|
|
2926
2907
|
}
|
|
2927
2908
|
|
|
2928
2909
|
function validateProviderDeclaration(config: ProviderDeclaration): void {
|
|
@@ -2985,12 +2966,12 @@ function finalizeProvider<
|
|
|
2985
2966
|
fix: "Add at least one operation to the operations object",
|
|
2986
2967
|
});
|
|
2987
2968
|
validateOperationIds(config.id, config.operations);
|
|
2988
|
-
|
|
2969
|
+
validateOperationTimeouts(config.id, config.operations);
|
|
2989
2970
|
validateOperationObservability(config.id, config.operations);
|
|
2990
2971
|
validateOperationErrorCodes(config.id, config.operations);
|
|
2991
2972
|
validateOperationTransports(config.id, config.operations);
|
|
2992
2973
|
validateOperationContracts(config.id, config.operations);
|
|
2993
|
-
|
|
2974
|
+
validateOperationMetadata(config.id, config.operations);
|
|
2994
2975
|
const journeyCoveredOperations = validateHealthJourneys(
|
|
2995
2976
|
config.id,
|
|
2996
2977
|
config.operations,
|
|
@@ -3004,9 +2985,11 @@ function finalizeProvider<
|
|
|
3004
2985
|
id: config.id,
|
|
3005
2986
|
version: config.version,
|
|
3006
2987
|
runtime: config.runtime,
|
|
2988
|
+
runtimeTarget: config.runtimeTarget,
|
|
3007
2989
|
// Verbatim passthrough: deployment validation and profile resolution
|
|
3008
2990
|
// are owned by the APIFuse registry builder, not the SDK.
|
|
3009
2991
|
deployment: config.deployment,
|
|
2992
|
+
http: config.http,
|
|
3010
2993
|
allowedHosts: config.allowedHosts,
|
|
3011
2994
|
native: config.native,
|
|
3012
2995
|
stealth: config.stealth,
|
|
@@ -3016,11 +2999,16 @@ function finalizeProvider<
|
|
|
3016
2999
|
resolver: config.resolver,
|
|
3017
3000
|
browser: config.browser,
|
|
3018
3001
|
auth: config.auth,
|
|
3002
|
+
choice: config.choice,
|
|
3019
3003
|
reviewed: config.reviewed,
|
|
3020
3004
|
access: config.access,
|
|
3021
3005
|
secrets: config.secrets,
|
|
3006
|
+
env: config.env,
|
|
3022
3007
|
credential: config.credential,
|
|
3023
3008
|
context: config.context,
|
|
3009
|
+
state: config.state,
|
|
3010
|
+
cache: config.cache,
|
|
3011
|
+
files: config.files,
|
|
3024
3012
|
meta: config.meta,
|
|
3025
3013
|
operations,
|
|
3026
3014
|
// Transitional healthMonitor → healthProbe alias: mirror whichever field
|
package/src/dev.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { serve } from "./server/serve.js";
|
|
2
|
+
import type { ProviderEngine } from "./engine.js";
|
|
2
3
|
import type { ProviderDefinition } from "./types.js";
|
|
3
4
|
|
|
4
5
|
export interface DevServerOptions {
|
|
5
6
|
port?: number;
|
|
6
7
|
sessionDbPath?: string;
|
|
8
|
+
/** Override the default in-process engine attachment. */
|
|
9
|
+
engine?: ProviderEngine;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
export function createDevServer(
|
|
@@ -14,7 +17,7 @@ export function createDevServer(
|
|
|
14
17
|
|
|
15
18
|
return {
|
|
16
19
|
start: () => {
|
|
17
|
-
void serve(provider, { port });
|
|
20
|
+
void serve(provider, { port, engine: options?.engine });
|
|
18
21
|
console.log(
|
|
19
22
|
`[apifuse dev] ${provider.id}@${provider.version} running at http://localhost:${port}`,
|
|
20
23
|
);
|