@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
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 {
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 (
|
|
1376
|
+
if (!hasDescriptionKey) {
|
|
1377
1377
|
diagnostics.push({
|
|
1378
|
-
rule: "
|
|
1378
|
+
rule: "description-key-required",
|
|
1379
1379
|
level: "error",
|
|
1380
|
-
field: "
|
|
1381
|
-
message: "Operation
|
|
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
|
-
|
|
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
|
-
|
|
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,
|