@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.10
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 +240 -0
- package/CHANGELOG.md +93 -0
- package/README.md +26 -10
- package/SUBMISSION.md +11 -12
- package/bin/apifuse-check.ts +44 -59
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-check.ts +36 -0
- package/bin/apifuse-pack-smoke.ts +22 -81
- package/bin/apifuse-pack-types.ts +266 -0
- package/bin/apifuse-perf.ts +45 -127
- package/bin/apifuse-record.ts +53 -70
- package/bin/apifuse-submit-check.ts +2177 -353
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml-semantics.ts +204 -0
- package/bin/submit-check-xml.ts +134 -0
- package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
- package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/dist/auth-turn/fixtures/valid/abort.json +8 -0
- package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
- package/dist/auth-turn/fixtures/valid/complete.json +13 -0
- package/dist/auth-turn/fixtures/valid/form.json +14 -0
- package/dist/auth-turn/fixtures/valid/message.json +13 -0
- package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/dist/auth-turn/fixtures/valid/pending.json +5 -0
- package/dist/auth-turn/fixtures/valid/poll.json +9 -0
- package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
- package/dist/auth-turn/fixtures/valid/retry.json +8 -0
- package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/dist/auth-turn/index.d.ts +195 -0
- package/dist/auth-turn/index.js +133 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +427 -0
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +14 -48
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +47 -33
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/dist/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/dist/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
- package/dist/cli/templates/provider/.gitignore.tpl +22 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
- package/dist/cli/templates/provider/README.md.tpl +163 -0
- package/dist/cli/templates/provider/dev.ts.tpl +5 -0
- package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
- package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
- package/dist/cli/templates/provider/index.ts.tpl +15 -0
- package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/dist/cli/templates/provider/meta.ts.tpl +7 -0
- package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/config/loader.d.ts +149 -8
- package/dist/config/loader.js +378 -89
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +3 -6
- package/dist/contract-types.d.ts +2 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +5 -6
- package/dist/define.d.ts +13 -1
- package/dist/define.js +245 -178
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.js +48 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +44 -41
- package/dist/index.js +39 -36
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +35 -15
- package/dist/provider.d.ts +11 -9
- package/dist/provider.js +9 -8
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +1 -1
- package/dist/runtime/auth-flow.js +4 -2
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +214 -25
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.js +4 -8
- package/dist/runtime/choice.d.ts +1 -1
- package/dist/runtime/choice.js +31 -35
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +15 -3
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +160 -344
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +56 -19
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +35 -0
- package/dist/runtime/proxy-nodemaven.js +128 -0
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +55 -52
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +2 -4
- package/dist/runtime/request-options.d.ts +1 -1
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +15 -4
- package/dist/runtime/stealth.d.ts +7 -4
- package/dist/runtime/stealth.js +257 -215
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +11 -15
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -3
- package/dist/server/index.js +6 -2
- package/dist/server/self-test-input-tokens.d.ts +1 -0
- package/dist/server/self-test-input-tokens.js +37 -0
- package/dist/server/self-test-redaction.d.ts +20 -0
- package/dist/server/self-test-redaction.js +70 -0
- package/dist/server/self-test-token.d.ts +30 -0
- package/dist/server/self-test-token.js +50 -0
- package/dist/server/self-test.d.ts +199 -0
- package/dist/server/self-test.js +1113 -0
- package/dist/server/serve.d.ts +14 -3
- package/dist/server/serve.js +135 -64
- package/dist/server/types.d.ts +10 -9
- package/dist/server/types.js +3 -7
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +5 -14
- package/dist/stream.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/run.d.ts +1 -1
- package/dist/testing/run.js +12 -15
- package/dist/types.d.ts +237 -1
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +16 -5
- package/src/auth-turn/auth-turn.v1.schema.json +89 -0
- package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/src/auth-turn/fixtures/valid/abort.json +8 -0
- package/src/auth-turn/fixtures/valid/challenge.json +17 -0
- package/src/auth-turn/fixtures/valid/complete.json +13 -0
- package/src/auth-turn/fixtures/valid/form.json +14 -0
- package/src/auth-turn/fixtures/valid/message.json +13 -0
- package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/src/auth-turn/fixtures/valid/pending.json +5 -0
- package/src/auth-turn/fixtures/valid/poll.json +9 -0
- package/src/auth-turn/fixtures/valid/redirect.json +16 -0
- package/src/auth-turn/fixtures/valid/retry.json +8 -0
- package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/src/auth-turn/index.ts +177 -0
- package/src/auth.ts +728 -0
- package/src/ceremonies/index.ts +33 -121
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +69 -99
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/src/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/src/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/src/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/src/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/src/cli/templates/provider/README.md.tpl +7 -4
- package/src/config/loader.ts +543 -208
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +13 -28
- package/src/define.ts +397 -528
- package/src/dev.ts +4 -9
- package/src/errors.ts +58 -5
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +64 -41
- package/src/lint.ts +122 -159
- package/src/provider.ts +40 -9
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +6 -6
- package/src/runtime/browser.ts +320 -151
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +79 -132
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +23 -20
- package/src/runtime/http.ts +216 -539
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +78 -65
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +178 -0
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/proxy-telemetry.ts +79 -77
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +41 -110
- package/src/runtime/stealth.ts +331 -369
- package/src/runtime/stt.ts +38 -94
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +1 -1
- package/src/server/index.ts +44 -3
- package/src/server/self-test-input-tokens.ts +46 -0
- package/src/server/self-test-redaction.ts +97 -0
- package/src/server/self-test-token.ts +70 -0
- package/src/server/self-test.ts +1450 -0
- package/src/server/serve.ts +206 -216
- package/src/server/types.ts +7 -19
- package/src/stealth/profiles.ts +10 -26
- package/src/stream.ts +8 -19
- package/src/testing/index.ts +2 -2
- package/src/testing/run.ts +24 -64
- package/src/types.ts +274 -1
- package/src/user-input.ts +118 -0
package/AUTHORING.md
CHANGED
|
@@ -18,6 +18,59 @@
|
|
|
18
18
|
|
|
19
19
|
Provider code is the declaration input to the internal platform registry. The public SDK owns provider authoring/runtime ergonomics; internal docs, deploy, and discovery projections are built downstream from those declarations. `bun run lint:providers` enforces provider authoring standards.
|
|
20
20
|
|
|
21
|
+
### User-input round-trips: never dead-end (`needs_input`)
|
|
22
|
+
|
|
23
|
+
A mutation MUST NOT fail for a problem the end user can resolve by choosing among live options (a required menu/course selection, a form question, a stale-but-recoverable state token). Throwing an error there strands the consuming agent: consumer error-shaping layers routinely strip error metadata, and a model that only sees "error" narrates failure to the user instead of relaying the choice.
|
|
24
|
+
|
|
25
|
+
Return the official success-shaped contract from `src/user-input.ts` instead (`ProviderNeedsInputPayload`, guard `isProviderNeedsInputPayload`):
|
|
26
|
+
|
|
27
|
+
- `status: "needs_input"` plus `required_selections` — only the still-pending questions, with human-readable `label`s and `valid_options` the agent relays verbatim. The agent never chooses for the user; anything with no real choice (agreement checkboxes, single-option required groups) is the provider's job to auto-answer.
|
|
28
|
+
- `selected_options` — selections already settled, echoed so the retry keeps them (copy them back and add the user's new answers).
|
|
29
|
+
- a freshly minted provider state token in the same response, so the retry never races an expired token.
|
|
30
|
+
|
|
31
|
+
No retry templates, next-action routing, or other agent choreography: provider payloads carry upstream-backed data only, and the consumer owns how the ask is phrased and how the retry call is shaped.
|
|
32
|
+
|
|
33
|
+
Declare the union in the operation `output` schema (`z.union([CreatedSchema, NeedsInputSchema])`). Reserve hard errors for genuinely unrecoverable flows (payment-gated, unsupported input kinds) and state the concrete reason in the error `message` itself, not only in `details`. Reference implementation: `providers/catchtable` `reserve` in the platform monorepo.
|
|
34
|
+
|
|
35
|
+
### Attempt tokens: the server carries the decisions
|
|
36
|
+
|
|
37
|
+
When a mutation needs more than one user decision (or one decision plus a
|
|
38
|
+
final go/no-go), do not make the agent re-send accumulated state across
|
|
39
|
+
rounds — weak models drop or corrupt it. Split the operation into a
|
|
40
|
+
**prepare/confirm pair** driven by a server-held attempt record:
|
|
41
|
+
|
|
42
|
+
- The prepare operation is non-destructive. A start call takes only the
|
|
43
|
+
scalar intent fields; every response returns a fresh `attempt_token`
|
|
44
|
+
referencing a server-side record (`ctx.choice.issue` with
|
|
45
|
+
`storage.mode: "server"`) that stores every settled decision. Continue
|
|
46
|
+
calls take `attempt_token` plus only the NEW answers.
|
|
47
|
+
- `needs_input` rounds list only the still-pending selections; settled
|
|
48
|
+
decisions may ride along in a display-only field but are never re-sent.
|
|
49
|
+
- When nothing is pending, the prepare operation returns `status: "ready"`
|
|
50
|
+
with a human-readable summary — the consumer's user-facing confirmation.
|
|
51
|
+
- The confirm operation is the only mutation and takes exactly
|
|
52
|
+
`{attempt_token}`. It re-validates everything live before executing and
|
|
53
|
+
returns `needs_input` (fresh token) instead of proceeding when upstream
|
|
54
|
+
drift invalidates a stored decision — never substitute a different option
|
|
55
|
+
for what the user picked.
|
|
56
|
+
- Expired or foreign tokens fail factually (nothing happened; start a new
|
|
57
|
+
attempt with the scalar fields) — no answer salvage from a dead token.
|
|
58
|
+
- **The provider must enforce consumption itself.** `ctx.choice` server
|
|
59
|
+
storage keeps tokens parseable until TTL — `parse` does not invalidate
|
|
60
|
+
them, so a confirm handler that only parses can be replayed into a second
|
|
61
|
+
booking or payment. After a successful execution, record the result under
|
|
62
|
+
the token's digest in `ctx.state` and make replays idempotent: a repeated
|
|
63
|
+
confirm returns the original created payload without touching upstream,
|
|
64
|
+
and later prepare rounds on the consumed token fail factually with the
|
|
65
|
+
existing reference. Record the result only after upstream success, so an
|
|
66
|
+
interrupted confirm stays retryable.
|
|
67
|
+
|
|
68
|
+
The invariant behind all of it: complex flow state is the system's job, not
|
|
69
|
+
the model's. The model carries exactly one opaque key between calls.
|
|
70
|
+
Reference implementations: `providers/catchtable` `reserve`/`reserve-confirm`
|
|
71
|
+
(including the consume-on-success guard) and `providers/modu-parking`
|
|
72
|
+
payment state tokens in the platform monorepo.
|
|
73
|
+
|
|
21
74
|
### Description template
|
|
22
75
|
|
|
23
76
|
Every operation `description` MUST be at least 150 characters and follow this structure:
|
|
@@ -256,6 +309,59 @@ External contributors are expected to submit standalone Provider source plus:
|
|
|
256
309
|
Maintainers own monorepo import under `providers/<id>/`, registry generation,
|
|
257
310
|
deployment projection checks, and release workflows.
|
|
258
311
|
|
|
312
|
+
### Declared secrets are SDK-enforced
|
|
313
|
+
|
|
314
|
+
Environment/secret presence validation is single-sourced in the SDK. Declare
|
|
315
|
+
every env secret the provider needs in `defineProvider`:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
secrets: [
|
|
319
|
+
{
|
|
320
|
+
name: "APIFUSE__PROVIDER__MY_PROVIDER__API_KEY",
|
|
321
|
+
required: true,
|
|
322
|
+
description: "Upstream API key from the vendor portal",
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
The runtime validates every `required: true` declaration before any operation
|
|
328
|
+
handler or auth-flow handler (except `abort`) runs. When a required secret is
|
|
329
|
+
unset or whitespace-only, the invocation fails with the canonical structured
|
|
330
|
+
error — code `MISSING_SECRET`, HTTP 400, `details.category:
|
|
331
|
+
"credential_unavailable"`, `retryable: false`, and a `fix` naming every missing
|
|
332
|
+
secret — across `/v1/{operation}`, self-test probes, `apifuse perf`, and
|
|
333
|
+
`apifuse record`. The server also emits a `provider_secrets_missing` warn log
|
|
334
|
+
at boot so unprovisioned deployments are visible immediately without crashing
|
|
335
|
+
the pod.
|
|
336
|
+
|
|
337
|
+
Provider-local presence re-validation is **deprecated**: do not write
|
|
338
|
+
`requireServiceKey`/`requireApiKey`-style guards that re-check `ctx.env.get()`
|
|
339
|
+
and throw a hand-rolled `CONFIGURATION_ERROR`/`MISSING_SECRET`. Those guards
|
|
340
|
+
are dead weight (the SDK gate runs first) and historically diverged into
|
|
341
|
+
inconsistent error shapes. The `sdk-owned-secret-presence` submit-check rule
|
|
342
|
+
flags them at warn level; acknowledge a deliberate exception with
|
|
343
|
+
`// @apifuse-allow sdk-owned-secret-presence: <reason>`.
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
// Before (deprecated): provider-local double validation
|
|
347
|
+
function requireServiceKey(ctx: ProviderContext): string {
|
|
348
|
+
const value = ctx.env.get(SERVICE_KEY_ENV);
|
|
349
|
+
if (!value?.trim()) {
|
|
350
|
+
throw new ProviderError(`Missing required provider secret: ${SERVICE_KEY_ENV}`, {
|
|
351
|
+
code: "CONFIGURATION_ERROR",
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return value;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// After: declare { name: SERVICE_KEY_ENV, required: true } and read directly.
|
|
358
|
+
const serviceKey = ctx.env.get(SERVICE_KEY_ENV);
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Note the asymmetry: the gate treats whitespace-only values as missing, but
|
|
362
|
+
`ctx.env.get()` still returns the raw value to handlers — trim at the point of
|
|
363
|
+
use if the upstream is whitespace-sensitive.
|
|
364
|
+
|
|
259
365
|
### Public local debugging checklist
|
|
260
366
|
|
|
261
367
|
- Operation smoke requests use the provider server envelope:
|
|
@@ -264,6 +370,140 @@ deployment projection checks, and release workflows.
|
|
|
264
370
|
- Credential-backed smoke requests pass local-only credential material in
|
|
265
371
|
`connection.secrets`. Keep real values in shell env or `.env`, never in source
|
|
266
372
|
or fixtures.
|
|
373
|
+
- Hand-written auth flows should use `ctx.auth` helpers and return exactly one
|
|
374
|
+
terminal/next turn from each handler: `ctx.auth.nextForm(...)` or
|
|
375
|
+
`ctx.auth.nextPoll(...)` to ask Gateway for the next user/system step,
|
|
376
|
+
`ctx.auth.complete(...)` to finish with `data.credential`, or
|
|
377
|
+
`ctx.auth.abort(...)` to stop safely. Keep abort `data` and `actionHint`
|
|
378
|
+
JSON-safe and secret-free; never include raw cookies, credentials, headers,
|
|
379
|
+
HTML, or upstream `Error` objects.
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
export default defineProvider({
|
|
383
|
+
id: "example-provider",
|
|
384
|
+
version: "1.0.0",
|
|
385
|
+
runtime: "standard",
|
|
386
|
+
auth: {
|
|
387
|
+
mode: "credentials",
|
|
388
|
+
flow: {
|
|
389
|
+
async start(ctx) {
|
|
390
|
+
return ctx.auth.nextForm({
|
|
391
|
+
fields: {
|
|
392
|
+
email: { type: "email", labelKey: "auth.email.label" },
|
|
393
|
+
password: { type: "password", labelKey: "auth.password.label" },
|
|
394
|
+
},
|
|
395
|
+
hintKey: "auth.signIn",
|
|
396
|
+
});
|
|
397
|
+
},
|
|
398
|
+
async continue(ctx, input) {
|
|
399
|
+
const result = await loginWithSubmittedFields(ctx, input);
|
|
400
|
+
if (result.blocked) {
|
|
401
|
+
return ctx.auth.abort({
|
|
402
|
+
code: "account_action_required",
|
|
403
|
+
retry: "after_user_action",
|
|
404
|
+
actionHint: { kind: "open_provider_app" },
|
|
405
|
+
message: "Approve the login in the provider app.",
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return ctx.auth.complete({
|
|
409
|
+
credential: { cookie: result.cookie },
|
|
410
|
+
metadata: { accountId: result.accountId },
|
|
411
|
+
});
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
credential: { keys: ["cookie"] },
|
|
416
|
+
// ...metadata and operations
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
- Credentials auth providers should use `defineCredentialsAuth()` instead of
|
|
421
|
+
hand-writing `auth.flow.start/continue`. The helper exposes one happy path:
|
|
422
|
+
declare form `fields`, declare `credentialKeys`, and put upstream login/session
|
|
423
|
+
creation in `login(ctx, input)`. It returns both `auth` and `credential` for
|
|
424
|
+
`defineProvider()` and builds the complete turn as `data.credential`, which is
|
|
425
|
+
the only value Gateway persists onto the connection.
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
import { defineCredentialsAuth, defineProvider } from "@apifuse/provider-sdk";
|
|
429
|
+
|
|
430
|
+
const credentialsAuth = defineCredentialsAuth({
|
|
431
|
+
fields: {
|
|
432
|
+
email: { type: "email", labelKey: "auth.email.label" },
|
|
433
|
+
password: { type: "password", labelKey: "auth.password.label" },
|
|
434
|
+
},
|
|
435
|
+
credentialKeys: ["cookie"] as const,
|
|
436
|
+
storesReusableSecret: true,
|
|
437
|
+
justification: "Session cookie is required for authenticated operations.",
|
|
438
|
+
async login(ctx, input) {
|
|
439
|
+
const cookie = await loginAndBuildSessionCookie(ctx, input);
|
|
440
|
+
return { credential: { cookie } };
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
export default defineProvider({
|
|
445
|
+
id: "example-provider",
|
|
446
|
+
version: "1.0.0",
|
|
447
|
+
runtime: "standard",
|
|
448
|
+
auth: credentialsAuth.auth,
|
|
449
|
+
credential: credentialsAuth.credential,
|
|
450
|
+
context: credentialsAuth.context,
|
|
451
|
+
// ...metadata and operations
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
For OTP, MFA, CAPTCHA handoff, or user-approved login, return a challenge from
|
|
456
|
+
`login()` instead of hand-writing `contextPatch`, `poll`, and final credential
|
|
457
|
+
turns. SDK stores the pending challenge in auth-flow context, returns the next
|
|
458
|
+
form/pending turn, and still persists only the final `data.credential`.
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
import {
|
|
462
|
+
credentialsAuthChallenge,
|
|
463
|
+
defineCredentialsAuth,
|
|
464
|
+
} from "@apifuse/provider-sdk";
|
|
465
|
+
|
|
466
|
+
const credentialsAuth = defineCredentialsAuth({
|
|
467
|
+
fields: {
|
|
468
|
+
email: { type: "email" },
|
|
469
|
+
password: { type: "password" },
|
|
470
|
+
},
|
|
471
|
+
credentialKeys: ["cookie"] as const,
|
|
472
|
+
async login(ctx, input) {
|
|
473
|
+
const result = await passwordLogin(ctx, input);
|
|
474
|
+
if (result.otpRequired) {
|
|
475
|
+
return credentialsAuthChallenge("otp", {
|
|
476
|
+
state: { transactionId: result.transactionId },
|
|
477
|
+
hintKey: "auth.otp.prompt",
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (result.manualApprovalRequired) {
|
|
481
|
+
return credentialsAuthChallenge("manualApproval", {
|
|
482
|
+
state: { transactionId: result.transactionId },
|
|
483
|
+
hintKey: "auth.manualApproval.openApp",
|
|
484
|
+
timing: { suggestedPollIntervalMs: 3000, maxWaitMs: 120000 },
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return { credential: { cookie: result.cookie } };
|
|
488
|
+
},
|
|
489
|
+
challenges: {
|
|
490
|
+
otp: {
|
|
491
|
+
fields: { otp: { type: "otp", labelKey: "auth.otp.label" } },
|
|
492
|
+
async verify(ctx, input, state) {
|
|
493
|
+
const result = await verifyOtp(ctx, state.transactionId, input.otp);
|
|
494
|
+
return { credential: { cookie: result.cookie } };
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
manualApproval: {
|
|
498
|
+
async poll(ctx, state) {
|
|
499
|
+
const result = await checkApproval(ctx, state.transactionId);
|
|
500
|
+
if (!result.approved) return null;
|
|
501
|
+
return { credential: { cookie: result.cookie } };
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
```
|
|
267
507
|
- Auth-flow debugging starts with `/auth/start`, continues with
|
|
268
508
|
`/auth/continue`, and carries returned `contextPatch` values into the next
|
|
269
509
|
request's `context`.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,98 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.10
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit c41bd919739e0293ae8fa4d72a8a32f034cef4b8.
|
|
6
|
+
|
|
7
|
+
## 2.2.0-beta.9
|
|
8
|
+
|
|
9
|
+
- Release candidate for main commit 5c78c8b (bundles #67 nodemaven required-secret + #68 transport vendor-advance).
|
|
10
|
+
|
|
11
|
+
## 2.2.0-beta.8
|
|
12
|
+
|
|
13
|
+
- Release candidate for main commit 9e8a3f028ee78b9cab29d4aa3f5494ac9cffa65f.
|
|
14
|
+
|
|
15
|
+
## 2.2.0-beta.7
|
|
16
|
+
|
|
17
|
+
- Release candidate for main commit 2ce4ea4bd36ce333eba8b3b474bf6e82b5e9216c.
|
|
18
|
+
|
|
19
|
+
## 2.2.0-beta.6
|
|
20
|
+
|
|
21
|
+
- Release candidate for main commit 17f4e41d44efe7c148ef875b950be4f2c7df1294.
|
|
22
|
+
|
|
23
|
+
## 2.2.0-beta.5
|
|
24
|
+
|
|
25
|
+
- Release candidate for main commit 82fa14e99a9af7edd44e3196aa3f4e87b4699edf.
|
|
26
|
+
|
|
27
|
+
## 2.2.0-beta.4
|
|
28
|
+
|
|
29
|
+
- Release candidate for main commit 73f2c6ec429c2fbce8ac458a67111e4844b99178.
|
|
30
|
+
|
|
31
|
+
## 2.2.0-beta.3
|
|
32
|
+
|
|
33
|
+
- Release candidate for main commit 74e8e18b502dd9b02dbf0d3e702f917570312fc0.
|
|
34
|
+
|
|
35
|
+
## 2.2.0-beta.2
|
|
36
|
+
|
|
37
|
+
- Release candidate for main commit ceefad020a1038eade542fd3b128667b39625f6f.
|
|
38
|
+
|
|
39
|
+
## 2.2.0-beta.1
|
|
40
|
+
|
|
41
|
+
- Release candidate for main commit 5056b8c89fe0fa8f10bafcd30f83bcc421d4b5c5.
|
|
42
|
+
|
|
43
|
+
## 2.1.0-beta.22
|
|
44
|
+
|
|
45
|
+
- Release candidate for main commit af84b1e91c408b69773468fdaef80a01a36707cf.
|
|
46
|
+
|
|
47
|
+
## 2.1.0-beta.21
|
|
48
|
+
|
|
49
|
+
- Release candidate for main commit 8124b3eb150cc7a73a86a95266b3747763a494ae.
|
|
50
|
+
|
|
51
|
+
## 2.1.0-beta.20
|
|
52
|
+
|
|
53
|
+
- Release candidate for main commit 2a07cc5aef0d517c3b01d20445105f1669446bd3.
|
|
54
|
+
|
|
55
|
+
## 2.1.0-beta.19
|
|
56
|
+
|
|
57
|
+
- Release candidate for main commit 71d76385f722b1202880f26b14ea3916a07852eb.
|
|
58
|
+
|
|
59
|
+
## 2.1.0-beta.18
|
|
60
|
+
|
|
61
|
+
- Release candidate for main commit 46f93ecf8c2c8aab1afd0f23f5f62a87cbb45f6d.
|
|
62
|
+
|
|
63
|
+
## 2.1.0-beta.17
|
|
64
|
+
|
|
65
|
+
- Release candidate for main commit c4466ffc8a30cb99740b50687a62b92cec9ba10e.
|
|
66
|
+
|
|
67
|
+
## 2.1.0-beta.16
|
|
68
|
+
|
|
69
|
+
- Release candidate for main commit c0d8a1c4be519e1c1abc59e0304efc64f371634d.
|
|
70
|
+
|
|
71
|
+
## Unreleased
|
|
72
|
+
|
|
73
|
+
- Add `arrayBuffer()` and `bytes()` to `HttpResponse` so `ctx.http` consumers can read binary-safe upstream bodies; internal response handling is now byte-first.
|
|
74
|
+
- Preserve identity-only operation `connectionId` values in `ProviderContext` without requiring credential material.
|
|
75
|
+
|
|
76
|
+
## 2.1.0-beta.15
|
|
77
|
+
|
|
78
|
+
- Release candidate for main commit 4f51232d87828082f117dcc9f0c257a46f37c040.
|
|
79
|
+
|
|
80
|
+
## 2.1.0-beta.14
|
|
81
|
+
|
|
82
|
+
- Release candidate for main commit 6eb132be6abf34ad9a70bfe28c6d26b36348ff4a.
|
|
83
|
+
|
|
84
|
+
## 2.1.0-beta.13
|
|
85
|
+
|
|
86
|
+
- Release candidate for main commit d7b12716f54781df3e40206144c167844a485f8f.
|
|
87
|
+
|
|
88
|
+
## 2.1.0-beta.12
|
|
89
|
+
|
|
90
|
+
- Release candidate for main commit 2bc1061c6a68facaa2efde08bee31bf5cd96945e.
|
|
91
|
+
|
|
92
|
+
## 2.1.0-beta.11
|
|
93
|
+
|
|
94
|
+
- Release candidate for main commit b98ddc5024698f8c79e05e3295f7c8c8e8fe5a8a.
|
|
95
|
+
|
|
3
96
|
## 2.1.0-beta.9
|
|
4
97
|
|
|
5
98
|
- Preserve raw stealth response bytes through the public SDK response wrapper.
|
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Removed legacy runtime paths are not supported:
|
|
|
56
56
|
cd my-provider
|
|
57
57
|
bun run check
|
|
58
58
|
bun run test
|
|
59
|
-
bun run submit-check
|
|
59
|
+
bun run submit-check -- --smoke
|
|
60
60
|
bun run dev
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -119,17 +119,33 @@ the bad request path; provider/runtime failures include `code`, `message`, and
|
|
|
119
119
|
local-only values through `connection.secrets`. Read them in handlers with
|
|
120
120
|
`ctx.credential.get("key")` or `ctx.credential.getAccessToken()`.
|
|
121
121
|
- **Provider env secrets**: declare `secrets[]`, set values in your shell or
|
|
122
|
-
`.env`, and read only those names through `ctx.env.get("NAME")`.
|
|
122
|
+
`.env`, and read only those names through `ctx.env.get("NAME")`. The SDK
|
|
123
|
+
enforces presence of `required: true` declarations before handlers and auth
|
|
124
|
+
flows run, failing the invocation with a structured `MISSING_SECRET` error
|
|
125
|
+
(HTTP 400, category `credential_unavailable`) — do not re-check presence in
|
|
126
|
+
handlers.
|
|
127
|
+
- **Credentials auth flows**: prefer `defineCredentialsAuth()` over hand-written
|
|
128
|
+
`auth.flow`. Declare the form fields and credential keys once, then put the
|
|
129
|
+
upstream login/session creation in `login(ctx, input)`. Return
|
|
130
|
+
`credentialsAuthChallenge("otp" | "manualApproval" | ...)` for MFA, CAPTCHA
|
|
131
|
+
handoff, or user-approved login branches. The helper returns
|
|
132
|
+
`{ auth, credential, context }` for `defineProvider()` and always completes
|
|
133
|
+
with `data.credential`, which is the value Gateway persists onto the
|
|
134
|
+
connection.
|
|
123
135
|
- **Auth flows**: call `/auth/start`, then `/auth/continue` with the same
|
|
124
136
|
`flowId`; preserve any returned `contextPatch` in the next local request's
|
|
125
137
|
`context` object.
|
|
126
138
|
- **Stealth-sensitive providers**: use `ctx.http` for normal JSON/REST calls and
|
|
127
|
-
`ctx.stealth.fetch()` when you need browser-like session or cookie control.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
`
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
`ctx.stealth.fetch()` when you need browser-like session or cookie control.
|
|
140
|
+
`ctx.stealth.fetch()` uses the impit-backed browser stealth transport and
|
|
141
|
+
accepts request controls for `params`, `proxy`, `timeout`, `profile`,
|
|
142
|
+
`redirect`, `throwOnHttpError`, and `stealth.insecureSkipVerify`. For login
|
|
143
|
+
flows that must inspect intermediate `Location`/`Set-Cookie` headers, create
|
|
144
|
+
a session with `ctx.stealth.createSession()` and use `session.redirects.run()`;
|
|
145
|
+
inspect accumulated cookies through `session.cookies`. Select an SDK stealth
|
|
146
|
+
`profile` such as `chrome-146`; do not tune JA3, HTTP/2 SETTINGS, or
|
|
147
|
+
pseudo-header order in provider code. Chrome/Firefox-style profiles are
|
|
148
|
+
supported; use `ctx.browser` when Safari-specific behavior is required.
|
|
133
149
|
- **Browser providers**: for TypeScript Providers use `runtime: "browser"` plus
|
|
134
150
|
`browser.engine: "playwright-stealth"`; `nodriver` is a Python-runtime path.
|
|
135
151
|
Install local browser assets with `bunx playwright install chromium` when
|
|
@@ -233,10 +249,10 @@ be replaced before recording fixtures.
|
|
|
233
249
|
Standalone providers include a pre-submission script:
|
|
234
250
|
|
|
235
251
|
```bash
|
|
236
|
-
bun run submit-check
|
|
252
|
+
bun run submit-check -- --smoke
|
|
237
253
|
```
|
|
238
254
|
|
|
239
|
-
This runs the public review-readiness evaluator and writes `submission-report.md`. The report contains provider metadata, a 100-point readiness score, hard blockers, warnings, checklist evidence, and remediation. Blockers override the score; fix them before posting bounty evidence.
|
|
255
|
+
This runs the public review-readiness evaluator and writes `submission-report.md`. The report contains provider metadata, a 100-point readiness score, hard blockers, warnings, checklist evidence, measured smoke details, and remediation. Blockers override the score; fix them before posting bounty evidence. `--smoke` boots the local dev server, checks `/health`, and POSTs every operation fixture. Set `APIFUSE__PROVIDER__*` env vars when live upstream credentials are available; without them, structured provider errors can still earn runtime-path smoke credit. See [`SUBMISSION.md`](./SUBMISSION.md) for the full public-only bounty submission checklist shipped in the npm package.
|
|
240
256
|
|
|
241
257
|
## Scope boundary
|
|
242
258
|
|
package/SUBMISSION.md
CHANGED
|
@@ -9,11 +9,11 @@ bunx @apifuse/provider-sdk@beta create my-provider --yes
|
|
|
9
9
|
cd my-provider
|
|
10
10
|
bun run check
|
|
11
11
|
bun run test
|
|
12
|
-
bun run submit-check
|
|
12
|
+
bun run submit-check -- --smoke
|
|
13
13
|
bun run dev
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
`bun run submit-check` runs `apifuse submit-check . --markdown submission-report.md`
|
|
16
|
+
`bun run submit-check -- --smoke` runs the generated `apifuse submit-check . --markdown submission-report.md` script with measured smoke enabled.
|
|
17
17
|
|
|
18
18
|
## What submit-check scores
|
|
19
19
|
|
|
@@ -50,22 +50,21 @@ Fix all blockers before submitting:
|
|
|
50
50
|
- High-confidence secret or token material in source, README, package metadata, or fixtures.
|
|
51
51
|
- SDK-native source blockers: prefixed Provider ids, `vendor/` SDK shims or imports, raw `.describe()` prose instead of `describeKey`, raw global `fetch()` calls, and excessive `as Type` assertions.
|
|
52
52
|
|
|
53
|
-
Warnings do not fail the command, but they should be addressed when practical. For example, the generated starter `ping` operation warns because it is not a real upstream-backed bounty Operation. SDK-native warnings also flag moderate `as Type` assertion counts
|
|
53
|
+
Warnings do not fail the command, but they should be addressed when practical. For example, the generated starter `ping` operation warns because it is not a real upstream-backed bounty Operation. SDK-native warnings also flag moderate `as Type` assertion counts, credentialed Providers that never reference `ctx.credential`, and provider-local re-validation of declared env secrets (`sdk-owned-secret-presence`, 0 points): the SDK already rejects invocations with a structured `MISSING_SECRET` error when a declared `required: true` secret is unset, so `requireServiceKey`-style presence guards are deprecated dead weight — delete the guard and read the value directly with `ctx.env.get()`, or acknowledge a deliberate exception with `// @apifuse-allow sdk-owned-secret-presence: <reason>`.
|
|
54
54
|
|
|
55
|
-
##
|
|
55
|
+
## Measured local smoke
|
|
56
56
|
|
|
57
|
-
`submit-check` does not call
|
|
57
|
+
`submit-check` does not call live upstream APIs by default. For smoke scoring,
|
|
58
|
+
run measured smoke so the CLI boots the Provider, checks `/health`, and POSTs
|
|
59
|
+
each operation fixture:
|
|
58
60
|
|
|
59
61
|
```bash
|
|
60
|
-
bun run
|
|
61
|
-
curl -s http://localhost:3900/health
|
|
62
|
-
curl -s -X POST http://localhost:3900/v1/<operation> \
|
|
63
|
-
-H 'Content-Type: application/json' \
|
|
64
|
-
-d '{"requestId":"req_local_smoke","input":{...},"headers":{}}'
|
|
65
|
-
|
|
66
|
-
bun run submit-check -- --smoke-note "GET /health and POST /v1/<operation> passed locally with redacted input."
|
|
62
|
+
bun run submit-check -- --smoke
|
|
67
63
|
```
|
|
68
64
|
|
|
65
|
+
`APIFUSE__PROVIDER__*` env vars enable live upstream calls. Their absence can
|
|
66
|
+
still produce 7/10 smoke credit when every operation routes and returns a
|
|
67
|
+
structured provider error envelope, but zero schema-valid successes are observed.
|
|
69
68
|
Never paste real credentials, personal data, account numbers, access tokens, cookies, or unredacted upstream responses into workspace PR comments, chat, or reports.
|
|
70
69
|
|
|
71
70
|
## Submission evidence checklist
|
package/bin/apifuse-check.ts
CHANGED
|
@@ -6,9 +6,14 @@ import { pathToFileURL } from "node:url";
|
|
|
6
6
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
import {
|
|
10
|
+
formatPromptAssetIssues,
|
|
11
|
+
PROMPT_ASSET_SYNC_REMEDIATION,
|
|
12
|
+
verifyPromptAssets,
|
|
13
|
+
} from "../src/cli/prompt-assets.js";
|
|
14
|
+
import type { ProviderDefinition } from "../src/index.js";
|
|
15
|
+
import { lintProvider, type ProviderLintMode } from "../src/lint.js";
|
|
16
|
+
import { safeParseSchemaSync } from "../src/schema.js";
|
|
12
17
|
|
|
13
18
|
const HELP_TEXT = `Usage: apifuse check [path]
|
|
14
19
|
Example: apifuse check providers/korea-air-quality
|
|
@@ -24,9 +29,7 @@ export type RunChecksOptions = {
|
|
|
24
29
|
lintMode?: ProviderLintMode;
|
|
25
30
|
};
|
|
26
31
|
|
|
27
|
-
type SafeParseResult =
|
|
28
|
-
| { success: true; data: unknown }
|
|
29
|
-
| { success: false; error: unknown };
|
|
32
|
+
type SafeParseResult = { success: true; data: unknown } | { success: false; error: unknown };
|
|
30
33
|
|
|
31
34
|
export async function main() {
|
|
32
35
|
const args = normalizeArgs(process.argv.slice(2));
|
|
@@ -63,7 +66,7 @@ function normalizeArgs(argv: string[]): string[] {
|
|
|
63
66
|
return argv[0] === "check" ? argv.slice(1) : argv;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
function resolveProviderRoot(inputPath: string): string {
|
|
69
|
+
export function resolveProviderRoot(inputPath: string): string {
|
|
67
70
|
const resolvedInput = resolveFromParents(inputPath);
|
|
68
71
|
|
|
69
72
|
if (!existsSync(resolvedInput)) {
|
|
@@ -131,9 +134,26 @@ export async function runChecks(
|
|
|
131
134
|
checkProviderMetadata(provider),
|
|
132
135
|
checkDockerfile(dockerfilePath),
|
|
133
136
|
checkPackageJson(packageJsonPath),
|
|
137
|
+
checkPromptAssets(providerRoot),
|
|
134
138
|
];
|
|
135
139
|
}
|
|
136
140
|
|
|
141
|
+
export const PROMPT_ASSETS_CHECK_MESSAGE =
|
|
142
|
+
"Agent prompt assets match the installed SDK version";
|
|
143
|
+
|
|
144
|
+
function checkPromptAssets(providerRoot: string): CheckResult {
|
|
145
|
+
const verification = verifyPromptAssets(providerRoot);
|
|
146
|
+
if (verification.ok) {
|
|
147
|
+
return { message: PROMPT_ASSETS_CHECK_MESSAGE, passed: true };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
message: PROMPT_ASSETS_CHECK_MESSAGE,
|
|
152
|
+
passed: false,
|
|
153
|
+
details: [...formatPromptAssetIssues(verification), PROMPT_ASSET_SYNC_REMEDIATION],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
137
157
|
function isScannableProviderSourceFile(relativePath: string): boolean {
|
|
138
158
|
return (
|
|
139
159
|
/\.(?:ts|tsx|js|jsx|mjs|cjs|sh|bash)$/.test(relativePath) ||
|
|
@@ -142,17 +162,12 @@ function isScannableProviderSourceFile(relativePath: string): boolean {
|
|
|
142
162
|
);
|
|
143
163
|
}
|
|
144
164
|
|
|
145
|
-
function collectProviderSourceFiles(
|
|
146
|
-
providerRoot: string,
|
|
147
|
-
): Record<string, string> {
|
|
165
|
+
function collectProviderSourceFiles(providerRoot: string): Record<string, string> {
|
|
148
166
|
const sources: Record<string, string> = {};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"build",
|
|
154
|
-
".next",
|
|
155
|
-
]);
|
|
167
|
+
// `.agents`/`.apifuse` are deliberately not skipped: managed content there
|
|
168
|
+
// is markdown/JSON (never matched by isScannableProviderSourceFile), and a
|
|
169
|
+
// planted `.ts`/`.sh` under those directories must stay in scanner scope.
|
|
170
|
+
const skipDirectories = new Set([".git", "node_modules", "dist", "build", ".next"]);
|
|
156
171
|
const visit = (directory: string) => {
|
|
157
172
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
158
173
|
const path = resolve(directory, entry.name);
|
|
@@ -162,10 +177,7 @@ function collectProviderSourceFiles(
|
|
|
162
177
|
}
|
|
163
178
|
continue;
|
|
164
179
|
}
|
|
165
|
-
if (
|
|
166
|
-
!entry.isFile() ||
|
|
167
|
-
!isScannableProviderSourceFile(path.slice(providerRoot.length + 1))
|
|
168
|
-
) {
|
|
180
|
+
if (!entry.isFile() || !isScannableProviderSourceFile(path.slice(providerRoot.length + 1))) {
|
|
169
181
|
continue;
|
|
170
182
|
}
|
|
171
183
|
sources[path.slice(providerRoot.length + 1)] = readFileSync(path, "utf8");
|
|
@@ -175,10 +187,7 @@ function collectProviderSourceFiles(
|
|
|
175
187
|
return sources;
|
|
176
188
|
}
|
|
177
189
|
|
|
178
|
-
function checkIndex(
|
|
179
|
-
indexPath: string,
|
|
180
|
-
provider: ProviderDefinition | undefined,
|
|
181
|
-
): CheckResult {
|
|
190
|
+
function checkIndex(indexPath: string, provider: ProviderDefinition | undefined): CheckResult {
|
|
182
191
|
if (!existsSync(indexPath)) {
|
|
183
192
|
return {
|
|
184
193
|
message: "index.ts exists and exports default defineProvider",
|
|
@@ -200,9 +209,7 @@ function checkIndex(
|
|
|
200
209
|
};
|
|
201
210
|
}
|
|
202
211
|
|
|
203
|
-
function checkOperations(
|
|
204
|
-
provider: ProviderDefinition | undefined,
|
|
205
|
-
): CheckResult {
|
|
212
|
+
function checkOperations(provider: ProviderDefinition | undefined): CheckResult {
|
|
206
213
|
if (!provider) {
|
|
207
214
|
return {
|
|
208
215
|
message: "All operations have handler, input, output",
|
|
@@ -277,20 +284,14 @@ function checkSchemas(provider: ProviderDefinition | undefined): CheckResult {
|
|
|
277
284
|
continue;
|
|
278
285
|
}
|
|
279
286
|
|
|
280
|
-
const requestResult = parseFixture(
|
|
281
|
-
operation.input,
|
|
282
|
-
operation.fixtures.request,
|
|
283
|
-
);
|
|
287
|
+
const requestResult = parseFixture(operation.input, operation.fixtures.request);
|
|
284
288
|
if (!requestResult.success) {
|
|
285
289
|
failures.push(
|
|
286
290
|
`${operationId}: request fixture invalid (${formatSchemaError(requestResult.error)})`,
|
|
287
291
|
);
|
|
288
292
|
}
|
|
289
293
|
|
|
290
|
-
const responseResult = parseFixture(
|
|
291
|
-
operation.output,
|
|
292
|
-
operation.fixtures.response,
|
|
293
|
-
);
|
|
294
|
+
const responseResult = parseFixture(operation.output, operation.fixtures.response);
|
|
294
295
|
if (!responseResult.success) {
|
|
295
296
|
failures.push(
|
|
296
297
|
`${operationId}: response fixture invalid (${formatSchemaError(responseResult.error)})`,
|
|
@@ -317,13 +318,8 @@ function checkAuthoringLint(
|
|
|
317
318
|
};
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
const diagnostics = lintProvider(
|
|
321
|
-
|
|
322
|
-
{ mode: lintMode },
|
|
323
|
-
);
|
|
324
|
-
const errors = diagnostics.filter(
|
|
325
|
-
(diagnostic) => diagnostic.level === "error",
|
|
326
|
-
);
|
|
321
|
+
const diagnostics = lintProvider({ ...provider, providerSourceFiles }, { mode: lintMode });
|
|
322
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.level === "error");
|
|
327
323
|
const details = diagnostics.map((diagnostic) => {
|
|
328
324
|
const field = diagnostic.field ? `${diagnostic.field}: ` : "";
|
|
329
325
|
return `${diagnostic.level.toUpperCase()} ${diagnostic.rule} ${field}${diagnostic.message}`;
|
|
@@ -336,9 +332,7 @@ function checkAuthoringLint(
|
|
|
336
332
|
};
|
|
337
333
|
}
|
|
338
334
|
|
|
339
|
-
function checkProviderMetadata(
|
|
340
|
-
provider: ProviderDefinition | undefined,
|
|
341
|
-
): CheckResult {
|
|
335
|
+
function checkProviderMetadata(provider: ProviderDefinition | undefined): CheckResult {
|
|
342
336
|
if (!provider) {
|
|
343
337
|
return {
|
|
344
338
|
message: "Provider metadata is declared in defineProvider",
|
|
@@ -425,18 +419,12 @@ function checkPackageJson(packageJsonPath: string): CheckResult {
|
|
|
425
419
|
}
|
|
426
420
|
}
|
|
427
421
|
|
|
428
|
-
function assertProviderDefinition(
|
|
429
|
-
value: unknown,
|
|
430
|
-
): ProviderDefinition | undefined {
|
|
422
|
+
function assertProviderDefinition(value: unknown): ProviderDefinition | undefined {
|
|
431
423
|
return isProviderDefinition(value) ? value : undefined;
|
|
432
424
|
}
|
|
433
425
|
|
|
434
426
|
function isProviderDefinition(value: unknown): value is ProviderDefinition {
|
|
435
|
-
if (
|
|
436
|
-
!isRecord(value) ||
|
|
437
|
-
!isRecord(value.meta) ||
|
|
438
|
-
!isRecord(value.operations)
|
|
439
|
-
) {
|
|
427
|
+
if (!isRecord(value) || !isRecord(value.meta) || !isRecord(value.operations)) {
|
|
440
428
|
return false;
|
|
441
429
|
}
|
|
442
430
|
|
|
@@ -457,8 +445,7 @@ function hasSchemaParser(value: unknown): boolean {
|
|
|
457
445
|
return (
|
|
458
446
|
isRecord(value) &&
|
|
459
447
|
(typeof value.safeParse === "function" ||
|
|
460
|
-
(isRecord(value["~standard"]) &&
|
|
461
|
-
typeof value["~standard"].validate === "function"))
|
|
448
|
+
(isRecord(value["~standard"]) && typeof value["~standard"].validate === "function"))
|
|
462
449
|
);
|
|
463
450
|
}
|
|
464
451
|
|
|
@@ -470,9 +457,7 @@ function formatSchemaError(error: unknown): string {
|
|
|
470
457
|
if (Array.isArray(error)) {
|
|
471
458
|
return error
|
|
472
459
|
.map((issue) =>
|
|
473
|
-
isRecord(issue) && typeof issue.message === "string"
|
|
474
|
-
? issue.message
|
|
475
|
-
: String(issue),
|
|
460
|
+
isRecord(issue) && typeof issue.message === "string" ? issue.message : String(issue),
|
|
476
461
|
)
|
|
477
462
|
.join(", ");
|
|
478
463
|
}
|