@apifuse/provider-sdk 2.2.0-beta.48 → 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 +4 -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-vendors/capsolver.js +3 -3
- package/dist/runtime/resolver.js +3 -3
- 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 +111 -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-vendors/capsolver.ts +4 -3
- package/src/runtime/resolver.ts +3 -3
- 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 +109 -137
package/AUTHORING.md
CHANGED
|
@@ -73,45 +73,105 @@ payment state tokens in the platform monorepo.
|
|
|
73
73
|
|
|
74
74
|
### Description template
|
|
75
75
|
|
|
76
|
-
Every operation
|
|
76
|
+
Every operation declares a `descriptionKey`. Its English locale value should
|
|
77
|
+
follow this structure:
|
|
77
78
|
|
|
78
79
|
```
|
|
79
80
|
<What the tool does in one sentence>. Use when <specific scenarios>. Do NOT use for <counter-scenarios; point to alternatives>. Returns <key output fields>. <Important caveats: rate limits, auth, freshness>.
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
Example:
|
|
83
|
+
Example declaration and locale entry:
|
|
83
84
|
```ts
|
|
84
|
-
description
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"
|
|
90
|
-
|
|
85
|
+
descriptionKey: "operations.realtimeWeather.description",
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"operations": {
|
|
91
|
+
"realtimeWeather": {
|
|
92
|
+
"description": "Retrieves KMA ultra-short-term weather observations for a South Korean grid coordinate. Use when the user asks about current or hourly weather. Do NOT use for forecasts beyond two days; use the mid-range forecast operation instead. Returns hourly KST data; null values mean unavailable data."
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
91
96
|
```
|
|
92
97
|
|
|
93
98
|
### Language policy
|
|
94
99
|
|
|
95
|
-
- **Structural text**:
|
|
96
|
-
|
|
100
|
+
- **Structural text**: locale keys (`descriptionKey`, the other `*Key`/`*Keys`
|
|
101
|
+
fields, schema `.describeKey()`, and `examples[].scenarioKey`/`rationaleKey`).
|
|
102
|
+
- **Values only**: native language (fixtures payloads and `examples[].input`
|
|
103
|
+
values like "대방동", "KRW-BTC", entity catalog entries).
|
|
97
104
|
|
|
98
105
|
### Required per operation
|
|
99
106
|
|
|
100
|
-
- `
|
|
101
|
-
-
|
|
107
|
+
- `riskClass` — authored as `read`, `write`, `destructive`, or `external-send`
|
|
108
|
+
- `descriptionKey` — backed by every required provider locale catalog
|
|
109
|
+
- `connectionMode` — explicit for `credentials`, `oauth2`, and `oauth2_proxied` providers
|
|
110
|
+
- Every Zod field in input AND output has `.describeKey()` including nested objects + array items (error-level rule)
|
|
102
111
|
- `fixtures.request` + `fixtures.response` both present (error-level rule)
|
|
103
112
|
- Exactly one of `healthCheck`, `healthCheckUnsupported`, or `healthJourneys[].coversOperations` coverage per operation. Prefer `healthCheck` for safe read-only upstream probes; use `healthCheckUnsupported` only with a specific reason for destructive, paid, credential-sensitive, flaky, or otherwise unsafe probes. Use a provider-level health journey when a destructive or credential-sensitive flow can be proven safely only as a multi-step boundary test, such as stopping at a payment WebView URL.
|
|
104
113
|
|
|
114
|
+
### Capability declarations
|
|
115
|
+
|
|
116
|
+
Declare each capability used by provider operations. Capabilities without
|
|
117
|
+
configuration use a bare object, for example `http: {}`, `choice: {}`, or
|
|
118
|
+
`cache: {}`. `trace` is ambient and must not be declared: every operation
|
|
119
|
+
context receives it. `allowedHosts`, `proxy`, `secrets`, and `context` declare
|
|
120
|
+
policy or metadata only; they do not create context members and are not
|
|
121
|
+
capability bindings.
|
|
122
|
+
|
|
123
|
+
Declare `runtimeTarget: "vanilla"` for portable provider business logic. Use
|
|
124
|
+
`runtimeTarget: "engine"` only for an approved session-bearing provider that
|
|
125
|
+
must remain engine-resident. A vanilla target cannot declare `native`.
|
|
126
|
+
|
|
127
|
+
Proxy vendor application keys, usernames, and passwords are engine-owned. Do
|
|
128
|
+
not list `APIFUSE__PROXY__SMARTPROXY_APP_KEY`,
|
|
129
|
+
`APIFUSE__PROXY__NODEMAVEN_USERNAME`, or
|
|
130
|
+
`APIFUSE__PROXY__NODEMAVEN_PASSWORD` in provider `secrets`; `proxy` contains
|
|
131
|
+
policy intent only.
|
|
132
|
+
|
|
105
133
|
### Factored operations
|
|
106
134
|
|
|
107
135
|
`defineProvider(declaration)` returns the builder that accepts `operations`, so
|
|
108
136
|
inline handlers are typed only after capability declarations are fixed. Export
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
one declaration-derived context alias from the provider entry point:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import {
|
|
141
|
+
defineProvider,
|
|
142
|
+
type ProviderContext,
|
|
143
|
+
type ProviderDeclaration,
|
|
144
|
+
} from "@apifuse/provider-sdk/provider";
|
|
145
|
+
|
|
146
|
+
const declaration = {
|
|
147
|
+
// ...id, version, runtime, meta
|
|
148
|
+
http: {},
|
|
149
|
+
} as const satisfies ProviderDeclaration;
|
|
150
|
+
|
|
151
|
+
export type Ctx = ProviderContext<typeof declaration>;
|
|
152
|
+
const buildProvider = defineProvider(declaration);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Separate operation files need one provider-local type import and no generic
|
|
156
|
+
handler signature:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { defineOperation } from "@apifuse/provider-sdk/provider";
|
|
160
|
+
import type { Ctx } from "../index.js";
|
|
161
|
+
|
|
162
|
+
export const search = defineOperation<Ctx>()({
|
|
163
|
+
// ...input, output, fixtures, health check
|
|
164
|
+
async handler(ctx, input) {
|
|
165
|
+
return fetchSearch(ctx.http, input);
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`ProviderContextOf<typeof buildProvider>` remains an equivalent convenience
|
|
171
|
+
alias. Helpers should accept the one SDK client they use rather than the
|
|
172
|
+
provider context. Zod and Standard Schema v1 schemas retain input/output
|
|
173
|
+
inference. Invalid configs name the offending field, such as `auth.mode` or
|
|
174
|
+
`operations.<id>.fixtures.response`.
|
|
115
175
|
|
|
116
176
|
### Replay-safe fixtures
|
|
117
177
|
|
|
@@ -221,15 +281,11 @@ healthCheck: {
|
|
|
221
281
|
```
|
|
222
282
|
<!-- @magic-end:sample -->
|
|
223
283
|
|
|
224
|
-
### Strongly recommended (warn-level rules)
|
|
225
|
-
|
|
226
|
-
- `description` includes "use" AND "when" phrasing
|
|
227
|
-
- `inputExamples` with 2+ scenarios for complex input (nested objects, enums, format-sensitive strings)
|
|
228
|
-
- `derivations` for parameters not directly visible in the user query (e.g., `gridX` derived from geocoding)
|
|
229
|
-
|
|
230
284
|
### Optional but valuable
|
|
231
285
|
|
|
232
|
-
- `
|
|
286
|
+
- `examples`: usage examples with locale-keyed `scenarioKey`, an `input`, and optional `rationaleKey`
|
|
287
|
+
- `approval`: only when it deliberately differs from the `riskClass` default (`read → never`, `write → risk-based`, otherwise `always`)
|
|
288
|
+
- `titleKey`, `summaryKey`, `markdownKey`, `whenToUseKeys`, `whenNotToUseKeys`, and `normalizationNotesKeys`: locale-keyed operation prose
|
|
233
289
|
- `tags`: operation-level semantic tags for retrieval (e.g., `["weather", "korea", "realtime"]`)
|
|
234
290
|
- `relatedOperations`: `{ alternatives?: string[] }` — links to fallback/sibling operations
|
|
235
291
|
|
|
@@ -248,6 +304,7 @@ export default defineProvider({
|
|
|
248
304
|
})({
|
|
249
305
|
operations: {
|
|
250
306
|
verifyAudioOtp: {
|
|
307
|
+
riskClass: "read",
|
|
251
308
|
input: z.object({
|
|
252
309
|
audioBase64: z.string().describe("Base64-encoded short OTP audio"),
|
|
253
310
|
mediaType: z.string().optional().describe("Audio MIME type"),
|
|
@@ -422,7 +479,7 @@ Provider-server failures use a stable public envelope:
|
|
|
422
479
|
`retryable` is always present on responses emitted by the current SDK. Set
|
|
423
480
|
`retryable` in the `ProviderError` options when the provider knows the answer;
|
|
424
481
|
an explicit `true` or `false` wins over the matching operation declaration and
|
|
425
|
-
SDK derivation. When it is omitted, `operations.<id>.
|
|
482
|
+
SDK derivation. When it is omitted, `operations.<id>.errorCodes[].retryable`
|
|
426
483
|
is used for a matching provider-owned code, followed by SDK derivation (which
|
|
427
484
|
defaults ordinary `ProviderError` values to `false`). During stateful rolling
|
|
428
485
|
upgrades, the forwarding client also accepts an older owner response that omits
|
|
@@ -447,19 +504,17 @@ Treat this header as telemetry, not as provider-controlled public error detail.
|
|
|
447
504
|
Its category, taxonomy version, retryability, and optional upstream status match
|
|
448
505
|
the structured `provider_request_failed` log event.
|
|
449
506
|
|
|
450
|
-
Declare provider-owned operation failures
|
|
507
|
+
Declare provider-owned operation failures directly on the operation. The
|
|
451
508
|
server builds a lookup once at startup and applies it to failures from that
|
|
452
509
|
operation:
|
|
453
510
|
|
|
454
511
|
```ts
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}],
|
|
462
|
-
},
|
|
512
|
+
errorCodes: [{
|
|
513
|
+
code: "UPSTREAM_SCHEMA_ERROR",
|
|
514
|
+
status: 502,
|
|
515
|
+
retryable: true,
|
|
516
|
+
description: "The upstream response no longer matches its schema.",
|
|
517
|
+
}],
|
|
463
518
|
handler: async () => {
|
|
464
519
|
throw new ProviderError("Upstream schema changed", {
|
|
465
520
|
code: "UPSTREAM_SCHEMA_ERROR",
|
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -138,13 +138,14 @@ the bad request path; provider/runtime failures include `code`, `message`, and
|
|
|
138
138
|
- **Stealth-sensitive providers**: use `ctx.http` for normal JSON/REST calls and
|
|
139
139
|
`ctx.stealth.fetch()` when you need browser-like session or cookie control.
|
|
140
140
|
`ctx.stealth.fetch()` uses the `wreq-js`-backed browser stealth transport and
|
|
141
|
-
accepts request controls for `params`, `sensitiveParams`, `proxy`, `timeout`,
|
|
141
|
+
accepts request controls for `params`, `sensitiveParams`, `proxy`, `timeout`,
|
|
142
142
|
`maxBodyBytes`, `redirect`, `throwOnHttpError`, and
|
|
143
143
|
`stealth.insecureSkipVerify`. For login
|
|
144
144
|
flows that must inspect intermediate `Location`/`Set-Cookie` headers, create
|
|
145
145
|
a session with `ctx.stealth.createSession()` and use `session.redirects.run()`;
|
|
146
146
|
inspect accumulated cookies through `session.cookies`. Select an SDK stealth
|
|
147
|
-
|
|
147
|
+
identity with structured `stealth: { browser, os }` options; omitted `os`
|
|
148
|
+
explicitly defaults to `macos`. Do not pin a browser version
|
|
148
149
|
or tune JA3, HTTP/2 SETTINGS, or
|
|
149
150
|
pseudo-header order in provider code. Chrome, Firefox, and Safari profiles
|
|
150
151
|
are supported; use `ctx.browser` when the provider needs browser execution.
|
|
@@ -219,6 +220,7 @@ const buildProvider = defineProvider({
|
|
|
219
220
|
export type ProviderContext = ProviderContextOf<typeof buildProvider>
|
|
220
221
|
|
|
221
222
|
const search = defineOperation<ProviderContext>()({
|
|
223
|
+
riskClass: "read",
|
|
222
224
|
input: z.object({ q: z.string().describe("Search query") }),
|
|
223
225
|
output: z.object({ count: z.number().describe("Result count") }),
|
|
224
226
|
async handler(ctx, input) {
|
|
@@ -266,17 +268,17 @@ healthCheck: {
|
|
|
266
268
|
}
|
|
267
269
|
```
|
|
268
270
|
|
|
269
|
-
###
|
|
271
|
+
### Flat operation metadata
|
|
270
272
|
|
|
271
|
-
Operations declare
|
|
273
|
+
Operations declare access, safety, and execution metadata directly on the
|
|
274
|
+
operation. `riskClass` is required and is the safety source of truth:
|
|
272
275
|
|
|
273
276
|
| Field | Type | Notes |
|
|
274
277
|
|---|---|---|
|
|
275
|
-
| `
|
|
276
|
-
| `
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
279
|
-
| `rateLimit` | `{ calls, window }` | Per-operation rate hint. `window` is `"minute"\|"hour"\|"day"`. |
|
|
278
|
+
| `riskClass` | `"read" \| "write" \| "destructive" \| "external-send"` | Required safety classification. |
|
|
279
|
+
| `approval` | `"never" \| "risk-based" \| "always"` | Optional override; omit when it matches the risk-class default. |
|
|
280
|
+
| `connectionMode` | `"none" \| "optional" \| "required"` | Access requirement. Required by lint for credential-bearing providers. |
|
|
281
|
+
| `connectionExternalRefParam` | `string` | Public argument used to resolve a Connection. |
|
|
280
282
|
| `timeoutMs` | `number` | Per-operation upstream timeout (1–60000 ms). Omit to inherit the gateway global default. |
|
|
281
283
|
|
|
282
284
|
`defineProvider()` validates `timeoutMs` is an integer in `[1, 60000]` and throws `ValidationError` otherwise. The gateway applies the value via `context.WithTimeout` on every proxied call and clamps defensively to the same bound.
|
package/SUBMISSION.md
CHANGED
|
@@ -28,7 +28,7 @@ The score is a triage aid, not a payout guarantee. Maintainers still review corr
|
|
|
28
28
|
| Category | Points | Examples |
|
|
29
29
|
|---|---:|---|
|
|
30
30
|
| Definition & metadata | 15 | `defineProvider`, package, Dockerfile, SDK structural checks |
|
|
31
|
-
| Operations & schemas | 15 |
|
|
31
|
+
| Operations & schemas | 15 | locale-keyed descriptions, explicit risk classes, input/output schemas |
|
|
32
32
|
| Fixtures & tests | 15 | bidirectional fixtures that parse against schemas |
|
|
33
33
|
| Health coverage | 15 | real `healthCheck` or specific `healthCheckUnsupported.reason` |
|
|
34
34
|
| Runtime/local smoke | 10 | `/health` and at least one `POST /v1/{operation}` note |
|
package/bin/apifuse-dev.ts
CHANGED
|
@@ -6,13 +6,17 @@ import {
|
|
|
6
6
|
createCredentialContext,
|
|
7
7
|
createEnvContext,
|
|
8
8
|
createHttpClient,
|
|
9
|
+
createInProcessProviderEngine,
|
|
9
10
|
createOcrClientFromEnv,
|
|
10
11
|
createProviderCache,
|
|
11
12
|
createProviderChoiceContext,
|
|
13
|
+
createProviderEnvironment,
|
|
12
14
|
createUnsupportedResolverClient,
|
|
13
15
|
createSttClientFromEnv,
|
|
14
16
|
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
17
|
+
readEngineProxyCredentials,
|
|
15
18
|
type ProviderDefinition,
|
|
19
|
+
type ProviderEngineBindingCandidates,
|
|
16
20
|
ProviderError,
|
|
17
21
|
type ProviderProxyPolicy,
|
|
18
22
|
} from "../src/index.js";
|
|
@@ -79,19 +83,20 @@ export async function main() {
|
|
|
79
83
|
export function createProviderContext(provider: ProviderDefinition): {
|
|
80
84
|
ctx: ProviderContext;
|
|
81
85
|
} {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
const providerEnvironment = createProviderEnvironment(
|
|
87
|
+
process.env,
|
|
88
|
+
provider.secrets?.map((secret) => secret.name) ?? [],
|
|
89
|
+
);
|
|
90
|
+
const providerEnv = { get: (key: string) => providerEnvironment[key] };
|
|
91
|
+
const engineEnv = createEnvContext([PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV]);
|
|
92
|
+
const engineCredentials = readEngineProxyCredentials();
|
|
86
93
|
const credential = createCredentialContext();
|
|
87
94
|
const state = createMemoryProviderRuntimeState();
|
|
88
95
|
const cache = createProviderCache({ providerId: provider.id });
|
|
89
96
|
const proxyPolicy = resolveNativeProxyPolicy(provider);
|
|
90
|
-
const stealthProfile = provider.stealth
|
|
91
|
-
|
|
92
|
-
:
|
|
93
|
-
const ctx: ProviderContext = {
|
|
94
|
-
env,
|
|
97
|
+
const stealthProfile = provider.stealth ? getStealthProfile(provider.stealth) : undefined;
|
|
98
|
+
const candidates: ProviderEngineBindingCandidates = {
|
|
99
|
+
env: providerEnv,
|
|
95
100
|
credential,
|
|
96
101
|
auth: createUnsupportedAuthStub(),
|
|
97
102
|
browser:
|
|
@@ -105,11 +110,13 @@ export function createProviderContext(provider: ProviderDefinition): {
|
|
|
105
110
|
cache,
|
|
106
111
|
state,
|
|
107
112
|
trace: createTraceContext(),
|
|
108
|
-
stealth: createStealthClient("http://localhost"
|
|
113
|
+
stealth: createStealthClient("http://localhost", {
|
|
114
|
+
...(provider.stealth ? { stealth: provider.stealth } : {}),
|
|
115
|
+
}),
|
|
109
116
|
ocr: createOcrClientFromEnv(provider.ocr),
|
|
110
117
|
stt: createSttClientFromEnv(provider.stt),
|
|
111
118
|
resolver: provider.resolver
|
|
112
|
-
? createResolverClientFromEnv(provider.resolver,
|
|
119
|
+
? createResolverClientFromEnv(provider.resolver, engineCredentials, {
|
|
113
120
|
allowedHosts: provider.allowedHosts,
|
|
114
121
|
cache,
|
|
115
122
|
...(proxyPolicy
|
|
@@ -125,11 +132,15 @@ export function createProviderContext(provider: ProviderDefinition): {
|
|
|
125
132
|
: createUnsupportedResolverClient("Provider does not declare resolver capability"),
|
|
126
133
|
choice: createProviderChoiceContext({
|
|
127
134
|
providerId: provider.id,
|
|
128
|
-
env,
|
|
135
|
+
env: engineEnv,
|
|
129
136
|
credential,
|
|
130
137
|
state,
|
|
131
138
|
}),
|
|
132
|
-
}
|
|
139
|
+
};
|
|
140
|
+
const ctx = createInProcessProviderEngine().attach({
|
|
141
|
+
provider,
|
|
142
|
+
bindings: candidates,
|
|
143
|
+
}) as ProviderContext;
|
|
133
144
|
|
|
134
145
|
return { ctx };
|
|
135
146
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `apifuse migrate-operation-declaration [path] [--check] [--json]`
|
|
5
|
+
*
|
|
6
|
+
* Rewrites ADR-0009's legacy nested operation declarations to the flat
|
|
7
|
+
* authoring contract. The run is repository-atomic: any declaration the
|
|
8
|
+
* codemod cannot prove leaves every source file untouched and exits 2.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { migrateOperationDeclarationRepository } from "../src/cli/migrate-operation-declaration.js";
|
|
14
|
+
|
|
15
|
+
export async function main(): Promise<number> {
|
|
16
|
+
const args = process.argv.slice(3);
|
|
17
|
+
const check = args.includes("--check");
|
|
18
|
+
const json = args.includes("--json");
|
|
19
|
+
const positional = args.filter((argument) => !argument.startsWith("--"));
|
|
20
|
+
const providerRoot = resolve(positional[0] ?? ".");
|
|
21
|
+
const result = migrateOperationDeclarationRepository(providerRoot, { check });
|
|
22
|
+
|
|
23
|
+
if (json) {
|
|
24
|
+
console.log(
|
|
25
|
+
JSON.stringify({
|
|
26
|
+
schemaVersion: 1,
|
|
27
|
+
command: "migrate-operation-declaration",
|
|
28
|
+
...result,
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
} else if (result.status === "refused") {
|
|
32
|
+
console.error(
|
|
33
|
+
`migrate-operation-declaration: refused ${result.refusals.length} declaration(s); no files were written.`,
|
|
34
|
+
);
|
|
35
|
+
for (const item of result.refusals) {
|
|
36
|
+
console.error(` ${item.file} [${item.operationKey}] ${item.reason}: ${item.detail}`);
|
|
37
|
+
}
|
|
38
|
+
} else if (result.status === "unchanged") {
|
|
39
|
+
console.log("migrate-operation-declaration: already flat; nothing to do.");
|
|
40
|
+
} else {
|
|
41
|
+
const verb = result.status === "would-migrate" ? "would migrate" : "migrated";
|
|
42
|
+
const sidecar = result.sidecar === undefined ? "" : ` and ${result.sidecar}`;
|
|
43
|
+
console.log(
|
|
44
|
+
`migrate-operation-declaration: ${verb} ${result.operationCount} operation(s) across ${result.changedFiles.length} file(s)${sidecar}.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const exitCode = result.status === "refused" ? 2 : 0;
|
|
49
|
+
process.exitCode = exitCode;
|
|
50
|
+
return exitCode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (import.meta.main) {
|
|
54
|
+
process.exit(await main());
|
|
55
|
+
}
|
|
@@ -236,7 +236,7 @@ function smokePackedStealthNative(consumerDir: string): void {
|
|
|
236
236
|
"const address = server.address();",
|
|
237
237
|
'if (!address || typeof address === "string") throw new Error("Local server has no TCP address");',
|
|
238
238
|
'const baseUrl = "http://127.0.0.1:" + address.port;',
|
|
239
|
-
'const session = createStealthClient(baseUrl).createSession({
|
|
239
|
+
'const session = createStealthClient(baseUrl).createSession({ stealth: { browser: "safari", os: "macos" } });',
|
|
240
240
|
"try {",
|
|
241
241
|
' const response = await session.fetch("/native");',
|
|
242
242
|
' if (response.body !== "packed native stealth ok") throw new Error("Unexpected stealth body: " + response.body);',
|
|
@@ -80,6 +80,7 @@ const NEGATIVE_CONTROLS = [
|
|
|
80
80
|
"})({",
|
|
81
81
|
"\toperations: {",
|
|
82
82
|
"\t\tprobe: {",
|
|
83
|
+
'\t\t\triskClass: "read",',
|
|
83
84
|
"\t\t\tinput: z.object({}),",
|
|
84
85
|
"\t\t\toutput: z.object({ ok: z.boolean() }),",
|
|
85
86
|
"\t\t\thandler: async () => ({ ok: true }),",
|
|
@@ -436,7 +437,7 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
436
437
|
'export const cookieSolution: ChallengeSolution = { form: "cookies", cookies: { cf_clearance: "clearance" }, userAgent: "fixture-agent" };',
|
|
437
438
|
"export const invalidation = invalidateResolverSolution(providerContext.resolver, awsWafChallenge, cookieSolution);",
|
|
438
439
|
'export const resolverConfig: ProviderResolverConfig = { vendors: ["browser", "capsolver"], kinds: ["cloudflare_interstitial", "turnstile"] };',
|
|
439
|
-
'export const resolverProvider = defineProvider({ id: "pack-types-resolver", version: "1.0.0", runtime: "standard", resolver: resolverConfig, meta: { displayName: "Pack Types Resolver", descriptionKey: "meta.description", category: "test" } })({ operations: { probe: { input: z.object({}), output: z.object({ ok: z.boolean() }), handler: async () => ({ ok: true }), healthCheckUnsupported: { reason: "type fixture" } } } });',
|
|
440
|
+
'export const resolverProvider = defineProvider({ id: "pack-types-resolver", version: "1.0.0", runtime: "standard", resolver: resolverConfig, meta: { displayName: "Pack Types Resolver", descriptionKey: "meta.description", category: "test" } })({ operations: { probe: { riskClass: "read", input: z.object({}), output: z.object({ ok: z.boolean() }), handler: async () => ({ ok: true }), healthCheckUnsupported: { reason: "type fixture" } } } });',
|
|
440
441
|
"export const resolverContext: ResolverContext = { solve: async () => tokenSolution };",
|
|
441
442
|
'export const browserCookie: BrowserCookie = { name: "persistent-id", value: "persistent-token", domain: "example.com", path: "/", expires: 1786698176, httpOnly: true, secure: true };',
|
|
442
443
|
'const browserPage = undefined as unknown as Awaited<ReturnType<ProviderContext["browser"]["newPage"]>>;',
|
package/bin/apifuse-record.ts
CHANGED
|
@@ -8,8 +8,10 @@ import { pathToFileURL } from "node:url";
|
|
|
8
8
|
import {
|
|
9
9
|
createBypassProviderCache,
|
|
10
10
|
createHttpClient,
|
|
11
|
+
createInProcessProviderEngine,
|
|
11
12
|
createOcrClientFromEnv,
|
|
12
13
|
createProviderChoiceContext,
|
|
14
|
+
createProviderEnvironment,
|
|
13
15
|
createSttClientFromEnv,
|
|
14
16
|
createUnsupportedResolverClient,
|
|
15
17
|
executeOperation,
|
|
@@ -18,7 +20,9 @@ import {
|
|
|
18
20
|
type HttpStreamResponse,
|
|
19
21
|
type ProviderContext,
|
|
20
22
|
type ProviderDefinition,
|
|
23
|
+
type ProviderEngineBindingCandidates,
|
|
21
24
|
ProviderError,
|
|
25
|
+
readEngineProxyCredentials,
|
|
22
26
|
type ProviderProxyPolicy,
|
|
23
27
|
type RequestOptions,
|
|
24
28
|
type StealthClient,
|
|
@@ -449,7 +453,7 @@ export function createCaptureContext(
|
|
|
449
453
|
let capturedSse: { order: number; method: string; path: string } | undefined;
|
|
450
454
|
const sensitiveParamNames = new Set<string>();
|
|
451
455
|
const sensitiveParamValues = new Set<string>();
|
|
452
|
-
const captureSensitiveParams = (url: string, options?:
|
|
456
|
+
const captureSensitiveParams = (url: string, options?: SensitiveRequestOptions) => {
|
|
453
457
|
captureSensitiveRequestValues(url, options, sensitiveParamNames, sensitiveParamValues);
|
|
454
458
|
};
|
|
455
459
|
const getCapturedSensitiveParams = (): CapturedSensitiveParams => ({
|
|
@@ -503,15 +507,21 @@ export function createCaptureContext(
|
|
|
503
507
|
},
|
|
504
508
|
});
|
|
505
509
|
const stealth = proxyStealthClient(
|
|
506
|
-
createStealthClient(baseUrl
|
|
510
|
+
createStealthClient(baseUrl, {
|
|
511
|
+
...(provider.stealth ? { stealth: provider.stealth } : {}),
|
|
512
|
+
}),
|
|
507
513
|
captureSensitiveParams,
|
|
508
514
|
(order, response) => retainRawCapture(order, normalizeCapturedStealthResponse(response)),
|
|
509
515
|
reserveCaptureOrder,
|
|
510
516
|
);
|
|
511
517
|
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
518
|
+
const providerEnvironment = createProviderEnvironment(
|
|
519
|
+
process.env,
|
|
520
|
+
provider.secrets?.map((secret) => secret.name) ?? [],
|
|
521
|
+
);
|
|
522
|
+
const env = { get: (key: string) => providerEnvironment[key] };
|
|
523
|
+
const engineEnv = { get: (key: string) => process.env[key] };
|
|
524
|
+
const engineCredentials = readEngineProxyCredentials();
|
|
515
525
|
const credential = {
|
|
516
526
|
mode: "none" as const,
|
|
517
527
|
get: () => undefined,
|
|
@@ -522,10 +532,8 @@ export function createCaptureContext(
|
|
|
522
532
|
const state = createMemoryProviderRuntimeState();
|
|
523
533
|
const cache = createBypassProviderCache({ providerId: provider.id });
|
|
524
534
|
const proxyPolicy = resolveNativeProxyPolicy(provider);
|
|
525
|
-
const stealthProfile = provider.stealth
|
|
526
|
-
|
|
527
|
-
: undefined;
|
|
528
|
-
const ctx: ProviderContext = {
|
|
535
|
+
const stealthProfile = provider.stealth ? getStealthProfile(provider.stealth) : undefined;
|
|
536
|
+
const candidates: ProviderEngineBindingCandidates = {
|
|
529
537
|
env,
|
|
530
538
|
credential,
|
|
531
539
|
request: { headers: {} },
|
|
@@ -560,7 +568,7 @@ export function createCaptureContext(
|
|
|
560
568
|
ocr: createOcrClientFromEnv(provider.ocr),
|
|
561
569
|
stt: createSttClientFromEnv(provider.stt),
|
|
562
570
|
resolver: provider.resolver
|
|
563
|
-
? createResolverClientFromEnv(provider.resolver,
|
|
571
|
+
? createResolverClientFromEnv(provider.resolver, engineCredentials, {
|
|
564
572
|
allowedHosts: provider.allowedHosts,
|
|
565
573
|
cache,
|
|
566
574
|
...(proxyPolicy
|
|
@@ -576,12 +584,16 @@ export function createCaptureContext(
|
|
|
576
584
|
: createUnsupportedResolverClient("Provider does not declare resolver capability"),
|
|
577
585
|
choice: createProviderChoiceContext({
|
|
578
586
|
providerId: provider.id,
|
|
579
|
-
env,
|
|
587
|
+
env: engineEnv,
|
|
580
588
|
request: { headers: {} },
|
|
581
589
|
credential,
|
|
582
590
|
state,
|
|
583
591
|
}),
|
|
584
|
-
}
|
|
592
|
+
};
|
|
593
|
+
const ctx = createInProcessProviderEngine().attach({
|
|
594
|
+
provider,
|
|
595
|
+
bindings: candidates,
|
|
596
|
+
}) as ProviderContext;
|
|
585
597
|
|
|
586
598
|
return {
|
|
587
599
|
ctx,
|
|
@@ -636,9 +648,11 @@ type CapturedSensitiveParams = {
|
|
|
636
648
|
values: readonly string[];
|
|
637
649
|
};
|
|
638
650
|
|
|
651
|
+
type SensitiveRequestOptions = Pick<RequestOptions, "params" | "sensitiveParams">;
|
|
652
|
+
|
|
639
653
|
function captureSensitiveRequestValues(
|
|
640
654
|
url: string,
|
|
641
|
-
options:
|
|
655
|
+
options: SensitiveRequestOptions | undefined,
|
|
642
656
|
names: Set<string>,
|
|
643
657
|
values: Set<string>,
|
|
644
658
|
): void {
|
|
@@ -683,7 +697,7 @@ function captureSensitiveRequestValues(
|
|
|
683
697
|
}
|
|
684
698
|
}
|
|
685
699
|
|
|
686
|
-
function snapshotRequestOptions<T extends
|
|
700
|
+
function snapshotRequestOptions<T extends SensitiveRequestOptions>(options: T): T {
|
|
687
701
|
return {
|
|
688
702
|
...options,
|
|
689
703
|
...(options.params
|
|
@@ -778,7 +792,7 @@ type StealthSession = ReturnType<StealthClient["createSession"]>;
|
|
|
778
792
|
|
|
779
793
|
function proxyStealthClient(
|
|
780
794
|
client: StealthClient,
|
|
781
|
-
onSensitiveParams: (url: string, options?:
|
|
795
|
+
onSensitiveParams: (url: string, options?: SensitiveRequestOptions) => void,
|
|
782
796
|
onResponse: (order: number, response: Awaited<ReturnType<StealthClient["fetch"]>>) => void,
|
|
783
797
|
reserveOrder: () => number,
|
|
784
798
|
): StealthClient {
|
|
@@ -804,7 +818,7 @@ function proxyStealthClient(
|
|
|
804
818
|
|
|
805
819
|
function proxyStealthSession(
|
|
806
820
|
session: StealthSession,
|
|
807
|
-
onSensitiveParams: (url: string, options?:
|
|
821
|
+
onSensitiveParams: (url: string, options?: SensitiveRequestOptions) => void,
|
|
808
822
|
onResponse: (order: number, response: Awaited<ReturnType<StealthClient["fetch"]>>) => void,
|
|
809
823
|
reserveOrder: () => number,
|
|
810
824
|
): StealthSession {
|
|
@@ -2947,13 +2947,17 @@ function collectProviderRequiredLocaleKeys(provider: ProviderDefinition): string
|
|
|
2947
2947
|
|
|
2948
2948
|
for (const operation of Object.values(provider.operations)) {
|
|
2949
2949
|
addLocaleKeys(keys, [
|
|
2950
|
+
operation.titleKey,
|
|
2950
2951
|
operation.descriptionKey,
|
|
2951
|
-
operation.
|
|
2952
|
-
operation.
|
|
2953
|
-
operation.docs?.summaryKey,
|
|
2954
|
-
operation.docs?.markdownKey,
|
|
2952
|
+
operation.summaryKey,
|
|
2953
|
+
operation.markdownKey,
|
|
2955
2954
|
...(operation.whenToUseKeys ?? []),
|
|
2956
2955
|
...(operation.whenNotToUseKeys ?? []),
|
|
2956
|
+
...(operation.normalizationNotesKeys ?? []),
|
|
2957
|
+
...(operation.examples ?? []).flatMap((example) => [
|
|
2958
|
+
example.scenarioKey,
|
|
2959
|
+
example.rationaleKey,
|
|
2960
|
+
]),
|
|
2957
2961
|
...collectSchemaDescriptionKeys(operation.input),
|
|
2958
2962
|
...collectSchemaDescriptionKeys(operation.output),
|
|
2959
2963
|
]);
|
|
@@ -3020,8 +3024,8 @@ function scoreOperationMetadata(provider: ProviderDefinition): SubmitCheck {
|
|
|
3020
3024
|
return true;
|
|
3021
3025
|
})
|
|
3022
3026
|
.map(([operationId]) => operationId);
|
|
3023
|
-
const
|
|
3024
|
-
.filter(([, operation]) => !operation.
|
|
3027
|
+
const missingRiskClass = operations
|
|
3028
|
+
.filter(([, operation]) => !operation.riskClass)
|
|
3025
3029
|
.map(([operationId]) => operationId);
|
|
3026
3030
|
|
|
3027
3031
|
if (weakDescriptions.length > 0) {
|
|
@@ -3033,30 +3037,30 @@ function scoreOperationMetadata(provider: ProviderDefinition): SubmitCheck {
|
|
|
3033
3037
|
points: 0,
|
|
3034
3038
|
maxPoints: CATEGORY_MAX_POINTS.operations,
|
|
3035
3039
|
message: "One or more operations have weak descriptions.",
|
|
3036
|
-
remediation: `For ${weakDescriptions.join(", ")}, add an operation \`descriptionKey\` backed by \`locales/en.json\` and \`locales/ko.json
|
|
3040
|
+
remediation: `For ${weakDescriptions.join(", ")}, add an operation \`descriptionKey\` backed by \`locales/en.json\` and \`locales/ko.json\`.`,
|
|
3037
3041
|
evidence: weakDescriptions,
|
|
3038
3042
|
};
|
|
3039
3043
|
}
|
|
3040
3044
|
|
|
3041
|
-
const points =
|
|
3045
|
+
const points = missingRiskClass.length > 0 ? 11 : CATEGORY_MAX_POINTS.operations;
|
|
3042
3046
|
return {
|
|
3043
3047
|
id: "operation-metadata",
|
|
3044
3048
|
category: "operations",
|
|
3045
|
-
level:
|
|
3046
|
-
status:
|
|
3049
|
+
level: missingRiskClass.length > 0 ? "warn" : "info",
|
|
3050
|
+
status: missingRiskClass.length > 0 ? "warn" : "pass",
|
|
3047
3051
|
points,
|
|
3048
3052
|
maxPoints: CATEGORY_MAX_POINTS.operations,
|
|
3049
3053
|
message:
|
|
3050
|
-
|
|
3051
|
-
? "Operations are described, but some are missing safety
|
|
3054
|
+
missingRiskClass.length > 0
|
|
3055
|
+
? "Operations are described, but some are missing a safety classification."
|
|
3052
3056
|
: "Operation descriptions and metadata are review-ready.",
|
|
3053
3057
|
remediation:
|
|
3054
|
-
|
|
3055
|
-
? `For ${
|
|
3058
|
+
missingRiskClass.length > 0
|
|
3059
|
+
? `For ${missingRiskClass.join(", ")}, declare the operation's required \`riskClass\` as \`read\`, \`write\`, \`destructive\`, or \`external-send\`.`
|
|
3056
3060
|
: undefined,
|
|
3057
3061
|
evidence:
|
|
3058
|
-
|
|
3059
|
-
?
|
|
3062
|
+
missingRiskClass.length > 0
|
|
3063
|
+
? missingRiskClass.map((operationId) => `${operationId}: missing riskClass`)
|
|
3060
3064
|
: operations.map(([operationId]) => operationId),
|
|
3061
3065
|
};
|
|
3062
3066
|
}
|
|
@@ -5332,25 +5336,6 @@ function scoreAuthSafety(provider: ProviderDefinition): SubmitCheck {
|
|
|
5332
5336
|
};
|
|
5333
5337
|
}
|
|
5334
5338
|
|
|
5335
|
-
if (authMode === "none") {
|
|
5336
|
-
const securedOperations = Object.entries(provider.operations).filter(
|
|
5337
|
-
([, operation]) => operation.annotations?.openWorld === false,
|
|
5338
|
-
);
|
|
5339
|
-
if (securedOperations.length > 0) {
|
|
5340
|
-
return {
|
|
5341
|
-
id: "auth-safety",
|
|
5342
|
-
category: "auth",
|
|
5343
|
-
level: "warn",
|
|
5344
|
-
status: "warn",
|
|
5345
|
-
points: 7,
|
|
5346
|
-
maxPoints: CATEGORY_MAX_POINTS.auth,
|
|
5347
|
-
message: "Provider is no-auth but at least one operation is not marked openWorld.",
|
|
5348
|
-
remediation: `Either set \`auth.mode\` to the upstream auth model, or mark these public no-auth operations with \`annotations.openWorld: true\`: ${securedOperations.map(([operationId]) => operationId).join(", ")}.`,
|
|
5349
|
-
evidence: securedOperations.map(([operationId]) => operationId),
|
|
5350
|
-
};
|
|
5351
|
-
}
|
|
5352
|
-
}
|
|
5353
|
-
|
|
5354
5339
|
return pass(
|
|
5355
5340
|
"auth-safety",
|
|
5356
5341
|
"auth",
|