@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.
Files changed (114) hide show
  1. package/AUTHORING.md +91 -36
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +11 -9
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-dev.ts +24 -13
  6. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  7. package/bin/apifuse-pack-smoke.ts +1 -1
  8. package/bin/apifuse-pack-types.ts +2 -1
  9. package/bin/apifuse-record.ts +30 -16
  10. package/bin/apifuse-submit-check.ts +20 -35
  11. package/dist/cli/commands.d.ts +1 -1
  12. package/dist/cli/commands.js +11 -0
  13. package/dist/cli/migrate-operation-declaration.d.ts +59 -0
  14. package/dist/cli/migrate-operation-declaration.js +1178 -0
  15. package/dist/cli/templates/provider/README.md.tpl +3 -3
  16. package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -0
  17. package/dist/config/loader.d.ts +2 -0
  18. package/dist/config/loader.js +18 -7
  19. package/dist/contract-types.d.ts +11 -5
  20. package/dist/contract.js +21 -10
  21. package/dist/define.d.ts +25 -22
  22. package/dist/define.js +49 -75
  23. package/dist/dev.d.ts +3 -0
  24. package/dist/dev.js +1 -1
  25. package/dist/engine.d.ts +78 -0
  26. package/dist/engine.js +133 -0
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +2 -1
  29. package/dist/lint.d.ts +7 -15
  30. package/dist/lint.js +45 -70
  31. package/dist/provider.d.ts +3 -1
  32. package/dist/provider.js +1 -0
  33. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  34. package/dist/runtime/chrome149-header-order.js +289 -0
  35. package/dist/runtime/env.js +12 -0
  36. package/dist/runtime/executor.d.ts +2 -1
  37. package/dist/runtime/executor.js +3 -36
  38. package/dist/runtime/insights.js +2 -2
  39. package/dist/runtime/otlp.d.ts +71 -2
  40. package/dist/runtime/otlp.js +397 -16
  41. package/dist/runtime/resolver-public.d.ts +1 -1
  42. package/dist/runtime/resolver-public.js +1 -1
  43. package/dist/runtime/resolver-vendors/capsolver.js +9 -3
  44. package/dist/runtime/resolver-vendors/twocaptcha.js +1 -0
  45. package/dist/runtime/resolver.d.ts +12 -0
  46. package/dist/runtime/resolver.js +45 -11
  47. package/dist/runtime/stealth.d.ts +13 -4
  48. package/dist/runtime/stealth.js +362 -85
  49. package/dist/runtime/trace-config.js +2 -1
  50. package/dist/runtime/trace.d.ts +5 -0
  51. package/dist/runtime/trace.js +43 -10
  52. package/dist/server/self-test.d.ts +1 -3
  53. package/dist/server/self-test.js +2 -12
  54. package/dist/server/serve-implementation.d.ts +6 -1
  55. package/dist/server/serve-implementation.js +55 -40
  56. package/dist/server/trace-output.d.ts +3 -1
  57. package/dist/server/trace-output.js +61 -2
  58. package/dist/stealth/profiles.d.ts +9 -8
  59. package/dist/stealth/profiles.js +123 -286
  60. package/dist/types.d.ts +116 -108
  61. package/package.json +2 -1
  62. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
  63. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
  64. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
  65. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
  66. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
  67. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
  68. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
  69. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
  70. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
  71. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
  72. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
  73. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
  74. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
  75. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
  76. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
  77. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
  78. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
  79. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
  80. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
  81. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
  82. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
  83. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
  84. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
  85. package/src/cli/commands.ts +13 -0
  86. package/src/cli/migrate-operation-declaration.ts +1654 -0
  87. package/src/cli/templates/provider/README.md.tpl +3 -3
  88. package/src/cli/templates/provider/operations/ping.ts.tpl +2 -0
  89. package/src/config/loader.ts +31 -6
  90. package/src/contract-types.ts +11 -5
  91. package/src/contract.ts +21 -10
  92. package/src/define.ts +107 -119
  93. package/src/dev.ts +4 -1
  94. package/src/engine.ts +279 -0
  95. package/src/index.ts +13 -5
  96. package/src/lint.ts +58 -92
  97. package/src/provider.ts +25 -3
  98. package/src/runtime/chrome149-header-order.ts +330 -0
  99. package/src/runtime/env.ts +13 -0
  100. package/src/runtime/executor.ts +7 -40
  101. package/src/runtime/insights.ts +2 -2
  102. package/src/runtime/otlp.ts +467 -21
  103. package/src/runtime/resolver-public.ts +3 -0
  104. package/src/runtime/resolver-vendors/capsolver.ts +12 -4
  105. package/src/runtime/resolver-vendors/twocaptcha.ts +1 -0
  106. package/src/runtime/resolver.ts +68 -19
  107. package/src/runtime/stealth.ts +435 -103
  108. package/src/runtime/trace-config.ts +3 -2
  109. package/src/runtime/trace.ts +57 -17
  110. package/src/server/self-test.ts +2 -9
  111. package/src/server/serve-implementation.ts +89 -72
  112. package/src/server/trace-output.ts +99 -2
  113. package/src/stealth/profiles.ts +169 -327
  114. 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 an
117
- SDK stealth `profile`; the TypeScript stealth runtime uses `wreq-js` internally
118
- and supports Chrome, Firefox, and Safari profiles. Use `ctx.browser` only when
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,
@@ -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(vendor: ProxyVendorName): boolean {
809
- if (vendor === "nodemaven") return hasNodemavenCredentials();
810
- return Boolean(process.env[SMARTPROXY_APP_KEY_ENV]?.trim());
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 = process.env[SMARTPROXY_APP_KEY_ENV]?.trim();
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,
@@ -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 docs?: JsonValue;
40
+ readonly summaryKey?: JsonValue;
41
+ readonly markdownKey?: JsonValue;
35
42
  readonly whenToUseKeys?: JsonValue;
36
43
  readonly whenNotToUseKeys?: JsonValue;
37
- readonly derivations?: JsonValue;
38
- readonly inputExamples?: JsonValue;
39
- readonly annotations?: JsonValue;
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 docs = toJsonValue(operation.docs);
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 derivations = toJsonValue(operation.derivations);
90
- const inputExamples = toJsonValue(operation.inputExamples);
91
- const annotations = toJsonValue(operation.annotations);
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
- ...(docs === undefined ? {} : { docs }),
120
+ ...(summaryKey === undefined ? {} : { summaryKey }),
121
+ ...(markdownKey === undefined ? {} : { markdownKey }),
110
122
  ...(whenToUseKeys === undefined ? {} : { whenToUseKeys }),
111
123
  ...(whenNotToUseKeys === undefined ? {} : { whenNotToUseKeys }),
112
- ...(derivations === undefined ? {} : { derivations }),
113
- ...(inputExamples === undefined ? {} : { inputExamples }),
114
- ...(annotations === undefined ? {} : { annotations }),
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
- StealthPlatform,
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 that provider operations use the SDK HTTP client. */
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
- profile: string;
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 that provider operations issue and consume SDK choice tokens. */
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 that provider operations read SDK-managed environment values. */
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 that provider operations use SDK-managed persistent state. */
617
- state?: true;
618
- /** Declares that provider operations use the SDK provider cache. */
619
- cache?: true;
620
- /** Declares that provider operations access runtime-resolvable files. */
621
- files?: true;
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 validateToolRouterMetadata(
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
- const toolRouter = operation.toolRouter;
1241
- if (toolRouter === undefined) continue;
1242
- if (!toolRouter || typeof toolRouter !== "object") {
1243
- throw new ValidationError(
1244
- `Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter: must be an object.`,
1245
- {
1246
- fix: `Remove operations.${operationName}.toolRouter or provide MCP-safe metadata.`,
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
- toolRouter.riskClass,
1261
- `operations.${operationName}.toolRouter.riskClass`,
1262
- VALID_OPERATION_RISK_CLASSES,
1243
+ operation.approval,
1244
+ `operations.${operationName}.approval`,
1245
+ VALID_OPERATION_APPROVAL_POLICIES,
1263
1246
  providerId,
1264
1247
  );
1265
1248
  }
1266
- if (toolRouter.approval !== undefined) {
1249
+ if (operation.connectionMode !== undefined) {
1267
1250
  assertLiteralField(
1268
- toolRouter.approval,
1269
- `operations.${operationName}.toolRouter.approval`,
1270
- VALID_OPERATION_APPROVAL_POLICIES,
1251
+ operation.connectionMode,
1252
+ `operations.${operationName}.connectionMode`,
1253
+ VALID_OPERATION_CONNECTION_MODES,
1271
1254
  providerId,
1272
1255
  );
1273
1256
  }
1274
1257
  if (
1275
- toolRouter.connectionExternalRefParam !== undefined &&
1276
- (typeof toolRouter.connectionExternalRefParam !== "string" ||
1277
- toolRouter.connectionExternalRefParam.trim().length === 0)
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}.toolRouter.connectionExternalRefParam: must be a non-empty string.`,
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 validateOperationAnnotations(
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 annotations = operation.annotations;
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}.annotations.timeoutMs`;
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.docs?.errorCodes ?? []).entries()) {
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}.docs.errorCodes[${index}].status`;
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<TDeclaration extends ProviderDeclaration> = <
2865
+ export type ProviderBuilder<TConfig extends ProviderDeclaration> = <
2885
2866
  TOperations extends Record<string, ProviderOperation>,
2886
2867
  >(
2887
2868
  implementation: {
2888
- operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
2869
+ operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
2889
2870
  },
2890
2871
  ) => Omit<ProviderDefinition, "operations"> & {
2891
- operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
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 TDeclaration>
2896
- ? ProviderContextFor<TDeclaration>
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 TDeclaration extends ProviderDeclaration>(
2911
- declaration: TDeclaration &
2912
- Record<Exclude<keyof TDeclaration, keyof ProviderDeclaration>, never> &
2913
- AuthStartNoInputGuard<TDeclaration>,
2914
- ): ProviderBuilder<TDeclaration> {
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, ProviderContextFor<TDeclaration>>;
2899
+ operations: OperationMapConfig<TOperations, ProviderContext<TConfig>>;
2919
2900
  },
2920
2901
  ) =>
2921
2902
  finalizeProvider({
2922
2903
  ...declaration,
2923
2904
  ...implementation,
2924
- } as ProviderConfig<TOperations, ProviderContextFor<TDeclaration>>);
2925
- return buildProvider as ProviderBuilder<TDeclaration>;
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
- validateOperationAnnotations(config.id, config.operations);
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
- validateToolRouterMetadata(config.id, config.operations);
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
  );