@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
package/src/engine.ts ADDED
@@ -0,0 +1,279 @@
1
+ import { ProviderError } from "./errors.js";
2
+ import {
3
+ OTEL_EXPORTER_OTLP_ENDPOINT,
4
+ OTEL_EXPORTER_OTLP_HEADERS,
5
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
6
+ OTEL_EXPORTER_OTLP_TRACES_HEADERS,
7
+ OTEL_RESOURCE_ATTRIBUTES,
8
+ OTEL_SERVICE_NAME,
9
+ } from "./runtime/otlp.js";
10
+ import type {
11
+ AuthContext,
12
+ BrowserClient,
13
+ CredentialContext,
14
+ EnvContext,
15
+ HttpClient,
16
+ NativeContext,
17
+ OcrContext,
18
+ ProviderCache,
19
+ ProviderChoiceContext,
20
+ ProviderContext,
21
+ ProviderDefinition,
22
+ ProviderFilesContext,
23
+ ProviderRequestContext,
24
+ ProviderRuntimeState,
25
+ ResolverContext,
26
+ StealthClient,
27
+ SttContext,
28
+ TraceContext,
29
+ } from "./types.js";
30
+
31
+ /** Versioned envelope protocol used by out-of-process engine transports. */
32
+ export const PROVIDER_ENGINE_PROTOCOL_VERSION = "provider-engine.v1" as const;
33
+
34
+ /** Credential names owned by the engine and forbidden in provider declarations. */
35
+ export const ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES = [
36
+ "APIFUSE__PROXY__SMARTPROXY_APP_KEY",
37
+ "APIFUSE__PROXY__NODEMAVEN_USERNAME",
38
+ "APIFUSE__PROXY__NODEMAVEN_PASSWORD",
39
+ ] as const;
40
+
41
+ const ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAME_SET = new Set<string>(
42
+ ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES,
43
+ );
44
+
45
+ /**
46
+ * Environment names are compared case-insensitively: Windows resolves `otel_exporter_otlp_headers`
47
+ * to the same variable as `OTEL_EXPORTER_OTLP_HEADERS`, so a mixed-case alias must be treated as
48
+ * the engine-owned name it resolves to.
49
+ */
50
+ function canonicalEnvName(name: string): string {
51
+ return name.toUpperCase();
52
+ }
53
+
54
+ export function isEngineOwnedProxyCredentialName(name: string): boolean {
55
+ return ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAME_SET.has(canonicalEnvName(name));
56
+ }
57
+
58
+ /**
59
+ * Trace-export configuration owned by the engine and forbidden in provider
60
+ * declarations. The header variables carry collector credentials; the rest are
61
+ * engine deployment settings a provider has no reason to read.
62
+ */
63
+ export const ENGINE_OWNED_TELEMETRY_ENV_NAMES = [
64
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
65
+ OTEL_EXPORTER_OTLP_ENDPOINT,
66
+ OTEL_EXPORTER_OTLP_TRACES_HEADERS,
67
+ OTEL_EXPORTER_OTLP_HEADERS,
68
+ OTEL_SERVICE_NAME,
69
+ OTEL_RESOURCE_ATTRIBUTES,
70
+ ] as const;
71
+
72
+ const ENGINE_OWNED_TELEMETRY_ENV_NAME_SET = new Set<string>(ENGINE_OWNED_TELEMETRY_ENV_NAMES);
73
+
74
+ export function isEngineOwnedTelemetryEnvName(name: string): boolean {
75
+ return ENGINE_OWNED_TELEMETRY_ENV_NAME_SET.has(canonicalEnvName(name));
76
+ }
77
+
78
+ /** Every environment name the engine owns: rejected in declarations and filtered from provider projections. */
79
+ export function isEngineOwnedEnvName(name: string): boolean {
80
+ return isEngineOwnedProxyCredentialName(name) || isEngineOwnedTelemetryEnvName(name);
81
+ }
82
+
83
+ /** Capture credentials in the engine host before constructing provider bindings. */
84
+ export function readEngineProxyCredentials(
85
+ environment: Readonly<Record<string, string | undefined>> = process.env,
86
+ ): Readonly<Record<string, string>> {
87
+ return Object.fromEntries(
88
+ ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES.flatMap((name) => {
89
+ const value = environment[name]?.trim();
90
+ return value ? [[name, value] as const] : [];
91
+ }),
92
+ );
93
+ }
94
+
95
+ /** Build the exact environment projection permitted to enter a provider runtime. */
96
+ export function createProviderEnvironment(
97
+ environment: Readonly<Record<string, string | undefined>>,
98
+ declaredNames: readonly string[],
99
+ ): Readonly<Record<string, string>> {
100
+ return Object.fromEntries(
101
+ declaredNames.flatMap((name) => {
102
+ if (isEngineOwnedEnvName(name)) return [];
103
+ const value = environment[name];
104
+ return value === undefined ? [] : [[name, value] as const];
105
+ }),
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Request/response is the first remote transport lane. Stream and session
111
+ * handles are separate lanes so lifecycle-bearing capabilities are never
112
+ * disguised as ordinary JSON calls.
113
+ */
114
+ export interface ProviderEngineTransport {
115
+ request<TResponse = unknown>(request: ProviderEngineRequest): Promise<TResponse>;
116
+ openStream?(request: ProviderEngineRequest): Promise<ReadableStream<Uint8Array>>;
117
+ openSession?(request: ProviderEngineRequest): Promise<ProviderEngineSession>;
118
+ }
119
+
120
+ export interface ProviderEngineRequest {
121
+ readonly version: typeof PROVIDER_ENGINE_PROTOCOL_VERSION;
122
+ readonly providerId: string;
123
+ readonly requestId: string;
124
+ readonly capability: ProviderCapabilityKey;
125
+ readonly method: string;
126
+ readonly payload: unknown;
127
+ }
128
+
129
+ export interface ProviderEngineSession {
130
+ request<TResponse = unknown>(method: string, payload: unknown): Promise<TResponse>;
131
+ close(): Promise<void>;
132
+ }
133
+
134
+ /** Capability implementations owned by the engine rather than provider code. */
135
+ export interface ProviderEngineCapabilitySurface {
136
+ readonly http: HttpClient;
137
+ readonly browser: BrowserClient;
138
+ readonly stealth: StealthClient;
139
+ readonly stt: SttContext;
140
+ readonly ocr: OcrContext;
141
+ readonly resolver: ResolverContext;
142
+ readonly cache: ProviderCache;
143
+ readonly state: ProviderRuntimeState;
144
+ }
145
+
146
+ /** Engine-resident-only capabilities, kept separate from the portable surface. */
147
+ export interface ProviderEngineResidentSurface {
148
+ readonly native: NativeContext;
149
+ }
150
+
151
+ export const PROVIDER_CAPABILITY_KEYS = [
152
+ "env",
153
+ "credential",
154
+ "http",
155
+ "files",
156
+ "native",
157
+ "cache",
158
+ "state",
159
+ "stealth",
160
+ "browser",
161
+ "auth",
162
+ "ocr",
163
+ "stt",
164
+ "resolver",
165
+ "choice",
166
+ ] as const;
167
+
168
+ export type ProviderCapabilityKey = (typeof PROVIDER_CAPABILITY_KEYS)[number];
169
+
170
+ export type ProviderEngineBindingCandidates = Partial<
171
+ ProviderEngineCapabilitySurface &
172
+ ProviderEngineResidentSurface & {
173
+ readonly env: EnvContext;
174
+ readonly credential: CredentialContext;
175
+ readonly files: ProviderFilesContext;
176
+ readonly auth: AuthContext;
177
+ readonly choice: ProviderChoiceContext;
178
+ }
179
+ > & {
180
+ readonly request?: ProviderRequestContext;
181
+ readonly trace: TraceContext;
182
+ };
183
+
184
+ export interface ProviderEngineAttachmentInput {
185
+ readonly provider: ProviderDefinition;
186
+ readonly bindings: ProviderEngineBindingCandidates;
187
+ }
188
+
189
+ /** Attachment boundary shared by in-process development and remote RPC bridges. */
190
+ export interface ProviderEngine {
191
+ attach<TDeclaration extends object = Record<string, unknown>>(
192
+ input: ProviderEngineAttachmentInput,
193
+ ): ProviderContext<TDeclaration>;
194
+ }
195
+
196
+ const CAPABILITY_KEY_SET = new Set<string>(PROVIDER_CAPABILITY_KEYS);
197
+
198
+ function declaresCapability(
199
+ provider: ProviderDefinition,
200
+ capability: ProviderCapabilityKey,
201
+ ): boolean {
202
+ return Object.hasOwn(provider, capability) && provider[capability] !== undefined;
203
+ }
204
+
205
+ function attachmentError(
206
+ provider: ProviderDefinition,
207
+ capability: ProviderCapabilityKey,
208
+ ): ProviderError {
209
+ return new ProviderError(
210
+ `Provider engine could not attach declared capability "${capability}" for provider "${provider.id}"`,
211
+ {
212
+ code: "PROVIDER_ENGINE_ATTACHMENT_FAILED",
213
+ details: { providerId: provider.id, capability },
214
+ fix: `Configure the engine binding for "${capability}" before starting the provider.`,
215
+ },
216
+ );
217
+ }
218
+
219
+ function undeclaredCapabilityError(
220
+ provider: ProviderDefinition,
221
+ capability: ProviderCapabilityKey,
222
+ ): ProviderError {
223
+ return new ProviderError(
224
+ `Provider "${provider.id}" accessed undeclared capability "${capability}"; add the "${capability}" declaration`,
225
+ {
226
+ code: "PROVIDER_CAPABILITY_UNDECLARED",
227
+ details: { providerId: provider.id, capability },
228
+ fix: `Add ${capability}: {} to the provider declaration, or remove the access.`,
229
+ },
230
+ );
231
+ }
232
+
233
+ function attachInProcess<TDeclaration extends object>(
234
+ input: ProviderEngineAttachmentInput,
235
+ ): ProviderContext<TDeclaration> {
236
+ const { provider, bindings } = input;
237
+ if (provider.runtimeTarget === "vanilla" && declaresCapability(provider, "native")) {
238
+ throw new ProviderError(
239
+ `Provider "${provider.id}" cannot attach capability "native" to runtime target "vanilla"; native requires an engine-resident runtime`,
240
+ {
241
+ code: "PROVIDER_RUNTIME_CAPABILITY_CONFLICT",
242
+ details: { providerId: provider.id, capability: "native", runtimeTarget: "vanilla" },
243
+ },
244
+ );
245
+ }
246
+
247
+ const context: Record<PropertyKey, unknown> = { trace: bindings.trace };
248
+ if (bindings.request !== undefined) context.request = bindings.request;
249
+ for (const capability of PROVIDER_CAPABILITY_KEYS) {
250
+ if (!declaresCapability(provider, capability)) continue;
251
+ const binding = bindings[capability];
252
+ if (binding === undefined || binding === null) throw attachmentError(provider, capability);
253
+ context[capability] = binding;
254
+ }
255
+
256
+ return new Proxy(context, {
257
+ get(target, property, receiver) {
258
+ if (
259
+ typeof property === "string" &&
260
+ CAPABILITY_KEY_SET.has(property) &&
261
+ !declaresCapability(provider, property as ProviderCapabilityKey)
262
+ ) {
263
+ throw undeclaredCapabilityError(provider, property as ProviderCapabilityKey);
264
+ }
265
+ return Reflect.get(target, property, receiver);
266
+ },
267
+ has(target, property) {
268
+ if (typeof property === "string" && CAPABILITY_KEY_SET.has(property)) {
269
+ return declaresCapability(provider, property as ProviderCapabilityKey);
270
+ }
271
+ return Reflect.has(target, property);
272
+ },
273
+ }) as ProviderContext<TDeclaration>;
274
+ }
275
+
276
+ /** Local engine attachment; deployed bridges implement the same interface with RPC clients. */
277
+ export function createInProcessProviderEngine(): ProviderEngine {
278
+ return { attach: attachInProcess };
279
+ }
package/src/index.ts CHANGED
@@ -120,6 +120,7 @@ export type {
120
120
  export type { DevServerOptions } from "./dev.js";
121
121
  export { createDevServer, startDevServer } from "./dev.js";
122
122
  export * from "./errors.js";
123
+ export * from "./engine.js";
123
124
  export * from "./observability.js";
124
125
  export * from "./user-input.js";
125
126
  export * from "./i18n/index.js";
@@ -260,7 +261,12 @@ export {
260
261
  type ServeOptions,
261
262
  serve,
262
263
  } from "./server/serve.js";
263
- export { getStealthProfile, listStealthProfiles } from "./stealth/profiles.js";
264
+ export {
265
+ DEFAULT_STEALTH_BROWSER,
266
+ DEFAULT_STEALTH_OS,
267
+ getStealthProfile,
268
+ listStealthProfiles,
269
+ } from "./stealth/profiles.js";
264
270
  export * from "./stream.js";
265
271
  export type {
266
272
  ApiFuseResponse,
@@ -349,23 +355,20 @@ export type {
349
355
  OcrRecognizeRequest,
350
356
  OcrResult,
351
357
  OcrWarning,
352
- OperationAnnotations,
353
358
  OperationApprovalPolicy,
354
359
  OperationContractMetadata,
355
360
  OperationDefinition,
356
361
  OperationDeprecationMetadata,
357
- OperationDocMeta,
362
+ OperationExample,
358
363
  OperationErrorCode,
359
364
  ProviderErrorStatus,
360
365
  OperationHandlerResult,
361
- OperationInputExample,
362
366
  OperationLifecycle,
363
367
  OperationObservabilityConfig,
364
368
  OperationObservabilitySensitiveConfig,
365
369
  OperationRelationships,
366
370
  OperationRiskClass,
367
371
  OperationSensitivePath,
368
- OperationToolRouterMetadata,
369
372
  OperationTransport,
370
373
  OperationTransportKind,
371
374
  ProbeInterval,
@@ -413,6 +416,7 @@ export type {
413
416
  ProviderResolvedFile,
414
417
  ProviderResolverConfig,
415
418
  ProviderResolverVendor,
419
+ ProviderRuntimeTarget,
416
420
  ProviderRuntimeState,
417
421
  ProviderSecretDeclaration,
418
422
  ProviderStateDurationString,
@@ -437,11 +441,15 @@ export type {
437
441
  StateValue,
438
442
  StateWriteOptions,
439
443
  StealthClient,
444
+ StealthBrowser,
440
445
  StealthCookieStore,
441
446
  StealthCookieStoreV1,
442
447
  StealthFetchOptions,
443
448
  StealthPlatform,
444
449
  StealthProfile,
450
+ StealthProfileDescriptor,
451
+ StealthProfileSelection,
452
+ StealthOS,
445
453
  StealthRedirectHop,
446
454
  StealthRedirectRunOptions,
447
455
  StealthRedirectRunResult,
package/src/lint.ts CHANGED
@@ -8,6 +8,11 @@ import {
8
8
  } from "./error-resolution.js";
9
9
  import { lintPublicSchemaFieldNames } from "./public-schema-field-lint.js";
10
10
  import { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_SENSITIVE_META_KEY } from "./schema.js";
11
+ import type {
12
+ AuthMode,
13
+ OperationApprovalPolicy,
14
+ OperationRiskClass,
15
+ } from "./types.js";
11
16
 
12
17
  const requireModule = createRequire(import.meta.url);
13
18
  // `typeof import(...)` keeps the type without emitting a static import: the
@@ -28,6 +33,22 @@ type AuthModeLike =
28
33
  | "oauth2_proxied"
29
34
  | "api-key";
30
35
 
36
+ const CREDENTIAL_BEARING_AUTH_MODES = [
37
+ "credentials",
38
+ "oauth2",
39
+ "oauth2_proxied",
40
+ ] satisfies readonly AuthMode[];
41
+
42
+ function isCredentialBearingAuthMode(mode: AuthModeLike | undefined): boolean {
43
+ return CREDENTIAL_BEARING_AUTH_MODES.some((credentialMode) => credentialMode === mode);
44
+ }
45
+
46
+ function defaultApprovalPolicy(riskClass: OperationRiskClass): OperationApprovalPolicy {
47
+ if (riskClass === "read") return "never";
48
+ if (riskClass === "write") return "risk-based";
49
+ return "always";
50
+ }
51
+
31
52
  type ProviderAuthLike = {
32
53
  mode?: AuthModeLike;
33
54
  flow?: {
@@ -699,21 +720,6 @@ function collectSchemaDescriptionKeyDiagnostics(
699
720
  return diagnostics;
700
721
  }
701
722
 
702
- function isComplexSchema(schema: unknown, seen = new Set<SchemaLike>()): boolean {
703
- if (!isSchema(schema) || seen.has(schema)) {
704
- return false;
705
- }
706
-
707
- seen.add(schema);
708
- const children = getChildSchemas(schema);
709
- const hasNestedComposite = children.some(({ schema: child }) => {
710
- const childChildren = getChildSchemas(child);
711
- return childChildren.length > 0;
712
- });
713
-
714
- return hasNestedComposite || children.some(({ schema: child }) => isComplexSchema(child, seen));
715
- }
716
-
717
723
  function hasBidirectionalFixtures(fixtures: unknown): boolean {
718
724
  if (!fixtures || typeof fixtures !== "object") {
719
725
  return true;
@@ -1024,11 +1030,11 @@ function collectBrowserVersionLiteralFindings(source: string): BrowserVersionLit
1024
1030
  function browserVersionLiteralMessage(finding: BrowserVersionLiteralFinding): string {
1025
1031
  switch (finding.kind) {
1026
1032
  case "profile":
1027
- return `Hardcoded stealth profile "${finding.literal}" pins a browser version and will rot. Use the matching intent alias instead: "chrome-desktop", "firefox-desktop", "safari-desktop", or "safari-mobile".`;
1033
+ return `Hardcoded stealth profile "${finding.literal}" pins a browser version and will rot. Select the browser and OS structurally, for example stealth: { browser: "chrome", os: "macos" }.`;
1028
1034
  case "user-agent":
1029
- return `Hardcoded User-Agent browser version "${finding.literal}" can disagree with the stealth TLS fingerprint. Remove the literal and derive it from the matching intent profile, for example getStealthProfile("chrome-desktop").userAgent.`;
1035
+ return `Hardcoded User-Agent browser version "${finding.literal}" can disagree with the stealth TLS fingerprint. Remove the literal and derive it from the structured profile, for example getStealthProfile({ browser: "chrome", os: "macos" }).userAgent.`;
1030
1036
  case "sec-ch-ua":
1031
- return 'Hardcoded sec-ch-ua versions can disagree with the stealth TLS fingerprint. Remove the literal and let ctx.stealth generate client hints from an intent profile such as "chrome-desktop"; derive any explicit User-Agent with getStealthProfile("chrome-desktop").userAgent.';
1037
+ return 'Hardcoded sec-ch-ua versions can disagree with the stealth TLS fingerprint. Remove the literal and let ctx.stealth generate client hints from stealth: { browser: "chrome", os: "macos" }; derive any explicit User-Agent with getStealthProfile({ browser: "chrome", os: "macos" }).userAgent.';
1032
1038
  }
1033
1039
  }
1034
1040
 
@@ -1283,7 +1289,7 @@ function collectLiteralThrownErrorCodes(source: string): string[] {
1283
1289
  * `new ProviderError(...)` / `new ValidationError(...)` constructions whose
1284
1290
  * literal `code` is neither SDK-registered (SDK_RUNTIME_OWNED_ERROR_CODES
1285
1291
  * plus the canonical status-mapped codes shared with serve.ts toStatusCode)
1286
- * nor declared in any operation's docs.errorCodes. At runtime such a code
1292
+ * nor declared in any operation's errorCodes. At runtime such a code
1287
1293
  * serves HTTP 500 and emits the signal; this rule surfaces it at check time.
1288
1294
  *
1289
1295
  * A throw site cannot be attributed to a specific operation statically —
@@ -1303,7 +1309,7 @@ function lintUndeclaredThrownErrorCodes(provider: {
1303
1309
  {
1304
1310
  handler?: unknown;
1305
1311
  source?: string;
1306
- docs?: { errorCodes?: ReadonlyArray<{ code: string }> };
1312
+ errorCodes?: ReadonlyArray<{ code: string }>;
1307
1313
  }
1308
1314
  >;
1309
1315
  }): LintDiagnostic[] {
@@ -1312,7 +1318,7 @@ function lintUndeclaredThrownErrorCodes(provider: {
1312
1318
  ...SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES.keys(),
1313
1319
  ]);
1314
1320
  for (const operation of Object.values(provider.operations ?? {})) {
1315
- for (const entry of operation.docs?.errorCodes ?? []) {
1321
+ for (const entry of operation.errorCodes ?? []) {
1316
1322
  if (typeof entry?.code === "string") {
1317
1323
  knownCodes.add(entry.code);
1318
1324
  }
@@ -1349,7 +1355,7 @@ function lintUndeclaredThrownErrorCodes(provider: {
1349
1355
  rule: "thrown-error-code-undeclared",
1350
1356
  level: "warn",
1351
1357
  field,
1352
- message: `Thrown error code "${code}" (${field}) is neither SDK-registered nor declared in any operation's docs.errorCodes; at runtime it serves HTTP 500 and emits the unregistered_provider_error_code signal. Declare it in the owning operation's docs.errorCodes with status and retryable.`,
1358
+ message: `Thrown error code "${code}" (${field}) is neither SDK-registered nor declared in any operation's errorCodes; at runtime it serves HTTP 500 and emits the unregistered_provider_error_code signal. Declare it in the owning operation's errorCodes with status and retryable.`,
1353
1359
  });
1354
1360
  }
1355
1361
  }
@@ -1357,68 +1363,22 @@ function lintUndeclaredThrownErrorCodes(provider: {
1357
1363
  }
1358
1364
 
1359
1365
  export function lintOperation(op: {
1360
- description?: string;
1361
1366
  descriptionKey?: string;
1362
- whenToUse?: readonly string[];
1363
1367
  whenToUseKeys?: readonly string[];
1364
- whenNotToUse?: readonly string[];
1365
1368
  whenNotToUseKeys?: readonly string[];
1366
1369
  input: unknown;
1367
1370
  output: unknown;
1368
1371
  fixtures?: unknown;
1369
- inputExamples?: readonly unknown[];
1370
- derivations?: Record<string, string>;
1371
1372
  }): LintDiagnostic[] {
1372
1373
  const diagnostics: LintDiagnostic[] = [];
1373
- const description = op.description ?? "";
1374
1374
  const hasDescriptionKey = typeof op.descriptionKey === "string" && op.descriptionKey.length > 0;
1375
1375
 
1376
- if (description.trim().length > 0 && !hasDescriptionKey) {
1376
+ if (!hasDescriptionKey) {
1377
1377
  diagnostics.push({
1378
- rule: "operation-description-raw-prose",
1378
+ rule: "description-key-required",
1379
1379
  level: "error",
1380
- field: "description",
1381
- message: "Operation description must use descriptionKey instead of raw static prose.",
1382
- });
1383
- }
1384
-
1385
- if (!hasDescriptionKey && description.length < 150) {
1386
- diagnostics.push({
1387
- rule: "description-min-length",
1388
- level: "error",
1389
- field: "description",
1390
- message: "Operation description must be at least 150 characters.",
1391
- });
1392
- }
1393
-
1394
- if ((op.whenToUse?.length ?? 0) > 0 && !(op.whenToUseKeys?.length ?? 0)) {
1395
- diagnostics.push({
1396
- rule: "operation-when-to-use-raw-prose",
1397
- level: "error",
1398
- field: "whenToUse",
1399
- message: "Operation whenToUse must use whenToUseKeys instead of raw static prose.",
1400
- });
1401
- }
1402
-
1403
- if ((op.whenNotToUse?.length ?? 0) > 0 && !(op.whenNotToUseKeys?.length ?? 0)) {
1404
- diagnostics.push({
1405
- rule: "operation-when-not-to-use-raw-prose",
1406
- level: "error",
1407
- field: "whenNotToUse",
1408
- message: "Operation whenNotToUse must use whenNotToUseKeys instead of raw static prose.",
1409
- });
1410
- }
1411
-
1412
- const lowerDescription = description.toLowerCase();
1413
- if (
1414
- !hasDescriptionKey &&
1415
- !(lowerDescription.includes("use") && lowerDescription.includes("when"))
1416
- ) {
1417
- diagnostics.push({
1418
- rule: "description-has-when-clause",
1419
- level: "warn",
1420
- field: "description",
1421
- message: 'Operation description should include both "use" and "when".',
1380
+ field: "descriptionKey",
1381
+ message: "Operation must declare a locale-backed descriptionKey.",
1422
1382
  });
1423
1383
  }
1424
1384
 
@@ -1436,15 +1396,6 @@ export function lintOperation(op: {
1436
1396
  });
1437
1397
  }
1438
1398
 
1439
- if (isComplexSchema(op.input) && (op.inputExamples?.length ?? 0) < 2) {
1440
- diagnostics.push({
1441
- rule: "complex-input-has-examples",
1442
- level: "warn",
1443
- field: "inputExamples",
1444
- message: "Complex input schemas should provide at least 2 input examples.",
1445
- });
1446
- }
1447
-
1448
1399
  for (const field of uniqueFields(collectUnmarkedSensitiveFields(op.input, "input"))) {
1449
1400
  diagnostics.push({
1450
1401
  rule: "sensitive-field-unmarked",
@@ -1546,20 +1497,18 @@ export function lintProvider(
1546
1497
  operations?: Record<
1547
1498
  string,
1548
1499
  {
1549
- description?: string;
1550
1500
  descriptionKey?: string;
1551
- whenToUse?: readonly string[];
1552
1501
  whenToUseKeys?: readonly string[];
1553
- whenNotToUse?: readonly string[];
1554
1502
  whenNotToUseKeys?: readonly string[];
1503
+ connectionMode?: "none" | "optional" | "required";
1504
+ riskClass?: OperationRiskClass;
1505
+ approval?: OperationApprovalPolicy;
1555
1506
  input: unknown;
1556
1507
  output: unknown;
1557
1508
  fixtures?: unknown;
1558
- inputExamples?: readonly unknown[];
1559
- derivations?: Record<string, string>;
1560
1509
  handler?: unknown;
1561
1510
  source?: string;
1562
- docs?: { errorCodes?: ReadonlyArray<{ code: string }> };
1511
+ errorCodes?: ReadonlyArray<{ code: string }>;
1563
1512
  }
1564
1513
  >;
1565
1514
  meta?: {
@@ -1591,6 +1540,28 @@ export function lintProviderWithInformation(
1591
1540
 
1592
1541
  if (provider.operations) {
1593
1542
  const authMode = provider.auth?.mode;
1543
+ for (const [operationKey, operation] of Object.entries(provider.operations)) {
1544
+ if (isCredentialBearingAuthMode(authMode) && operation.connectionMode === undefined) {
1545
+ diagnostics.push({
1546
+ rule: "mixed-auth-connection-mode-required",
1547
+ level: "error",
1548
+ field: `operations.${operationKey}.connectionMode`,
1549
+ message: `Provider "${provider.id ?? "unknown"}" uses credential-bearing auth.mode "${authMode}"; operation "${operationKey}" must declare connectionMode explicitly.`,
1550
+ });
1551
+ }
1552
+ if (
1553
+ operation.riskClass !== undefined &&
1554
+ operation.approval !== undefined &&
1555
+ operation.approval === defaultApprovalPolicy(operation.riskClass)
1556
+ ) {
1557
+ diagnostics.push({
1558
+ rule: "redundant-approval",
1559
+ level: "error",
1560
+ field: `operations.${operationKey}.approval`,
1561
+ message: `Operation "${operationKey}" approval "${operation.approval}" repeats the default for riskClass "${operation.riskClass}"; omit approval unless it is a deliberate override.`,
1562
+ });
1563
+ }
1564
+ }
1594
1565
  // Every authenticated mode owns an auth.flow; `oauth2_proxied` was
1595
1566
  // previously exempt, which let auth-lifecycle operations ship on
1596
1567
  // proxied providers unchecked.
@@ -1620,17 +1591,12 @@ export function lintProviderWithInformation(
1620
1591
  ...Object.entries(provider.operations).flatMap(([operationKey, operation]) =>
1621
1592
  [
1622
1593
  ...lintOperation({
1623
- description: operation.description ?? "",
1624
1594
  descriptionKey: operation.descriptionKey,
1625
- whenToUse: operation.whenToUse,
1626
1595
  whenToUseKeys: operation.whenToUseKeys,
1627
- whenNotToUse: operation.whenNotToUse,
1628
1596
  whenNotToUseKeys: operation.whenNotToUseKeys,
1629
1597
  input: operation.input,
1630
1598
  output: operation.output,
1631
1599
  fixtures: operation.fixtures,
1632
- inputExamples: operation.inputExamples,
1633
- derivations: operation.derivations,
1634
1600
  }),
1635
1601
  ...lintPublicSchemaFieldNames(
1636
1602
  provider.id,
package/src/provider.ts CHANGED
@@ -129,6 +129,29 @@ export {
129
129
  TransportError,
130
130
  ValidationError,
131
131
  } from "./errors.js";
132
+ export {
133
+ createProviderEnvironment,
134
+ createInProcessProviderEngine,
135
+ ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES,
136
+ ENGINE_OWNED_TELEMETRY_ENV_NAMES,
137
+ isEngineOwnedEnvName,
138
+ isEngineOwnedProxyCredentialName,
139
+ isEngineOwnedTelemetryEnvName,
140
+ PROVIDER_CAPABILITY_KEYS,
141
+ PROVIDER_ENGINE_PROTOCOL_VERSION,
142
+ readEngineProxyCredentials,
143
+ } from "./engine.js";
144
+ export type {
145
+ ProviderCapabilityKey,
146
+ ProviderEngine,
147
+ ProviderEngineAttachmentInput,
148
+ ProviderEngineBindingCandidates,
149
+ ProviderEngineCapabilitySurface,
150
+ ProviderEngineRequest,
151
+ ProviderEngineResidentSurface,
152
+ ProviderEngineSession,
153
+ ProviderEngineTransport,
154
+ } from "./engine.js";
132
155
  export {
133
156
  getProviderLocalePath,
134
157
  providerLocaleKey,
@@ -203,17 +226,15 @@ export type {
203
226
  OperationApprovalPolicy,
204
227
  OperationContractMetadata,
205
228
  OperationDefinition,
206
- OperationDocMeta,
229
+ OperationExample,
207
230
  OperationErrorCode,
208
231
  ProviderErrorStatus,
209
- OperationInputExample,
210
232
  OperationLifecycle,
211
233
  OperationObservabilityConfig,
212
234
  OperationObservabilitySensitiveConfig,
213
235
  OperationRelationships,
214
236
  OperationRiskClass,
215
237
  OperationSensitivePath,
216
- OperationToolRouterMetadata,
217
238
  OperationTransport,
218
239
  ProviderAccessVisibility,
219
240
  ProviderChoiceBindingOptions,
@@ -238,6 +259,7 @@ export type {
238
259
  ProviderPublicConnectionMode,
239
260
  ProviderPublicProfile,
240
261
  ProviderResolvedFile,
262
+ ProviderRuntimeTarget,
241
263
  ProviderRuntimeState,
242
264
  ProviderStateDurationString,
243
265
  ProviderStateNamespace,