@apifuse/provider-sdk 2.2.0-beta.4 → 2.2.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/AUTHORING.md +92 -0
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +5 -1
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +26 -1
  6. package/bin/apifuse-pack-check.ts +14 -0
  7. package/bin/apifuse-submit-check.ts +433 -15
  8. package/bin/apifuse-sync-assets.ts +117 -0
  9. package/dist/cli/commands.d.ts +1 -1
  10. package/dist/cli/commands.js +8 -0
  11. package/dist/cli/create.d.ts +3 -0
  12. package/dist/cli/create.js +34 -35
  13. package/dist/cli/prompt-assets.d.ts +80 -0
  14. package/dist/cli/prompt-assets.js +743 -0
  15. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/runtime/executor.js +7 -0
  19. package/dist/runtime/secrets.d.ts +27 -0
  20. package/dist/runtime/secrets.js +51 -0
  21. package/dist/server/index.d.ts +1 -1
  22. package/dist/server/index.js +1 -1
  23. package/dist/server/self-test.d.ts +101 -0
  24. package/dist/server/self-test.js +670 -112
  25. package/dist/server/serve.d.ts +5 -0
  26. package/dist/server/serve.js +41 -1
  27. package/package.json +1 -1
  28. package/src/cli/commands.ts +10 -0
  29. package/src/cli/create.ts +42 -35
  30. package/src/cli/prompt-assets.ts +865 -0
  31. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  32. package/src/index.ts +5 -0
  33. package/src/runtime/executor.ts +8 -0
  34. package/src/runtime/secrets.ts +64 -0
  35. package/src/server/index.ts +5 -0
  36. package/src/server/self-test.ts +852 -127
  37. package/src/server/serve.ts +60 -1
  38. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  39. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  40. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  41. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  42. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  43. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  44. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  45. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  46. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  47. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  48. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  49. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  50. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  51. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
package/AUTHORING.md CHANGED
@@ -32,6 +32,45 @@ No retry templates, next-action routing, or other agent choreography: provider p
32
32
 
33
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
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
+
35
74
  ### Description template
36
75
 
37
76
  Every operation `description` MUST be at least 150 characters and follow this structure:
@@ -270,6 +309,59 @@ External contributors are expected to submit standalone Provider source plus:
270
309
  Maintainers own monorepo import under `providers/<id>/`, registry generation,
271
310
  deployment projection checks, and release workflows.
272
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
+
273
365
  ### Public local debugging checklist
274
366
 
275
367
  - Operation smoke requests use the provider server envelope:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.7
4
+
5
+ - Release candidate for main commit 2ce4ea4bd36ce333eba8b3b474bf6e82b5e9216c.
6
+
7
+ ## 2.2.0-beta.6
8
+
9
+ - Release candidate for main commit 17f4e41d44efe7c148ef875b950be4f2c7df1294.
10
+
11
+ ## 2.2.0-beta.5
12
+
13
+ - Release candidate for main commit 82fa14e99a9af7edd44e3196aa3f4e87b4699edf.
14
+
3
15
  ## 2.2.0-beta.4
4
16
 
5
17
  - Release candidate for main commit 73f2c6ec429c2fbce8ac458a67111e4844b99178.
package/README.md CHANGED
@@ -119,7 +119,11 @@ 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.
123
127
  - **Credentials auth flows**: prefer `defineCredentialsAuth()` over hand-written
124
128
  `auth.flow`. Declare the form fields and credential keys once, then put the
125
129
  upstream login/session creation in `login(ctx, input)`. Return
package/SUBMISSION.md CHANGED
@@ -50,7 +50,7 @@ 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 and credentialed Providers that never reference `ctx.credential`.
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
 
@@ -6,6 +6,11 @@ import { pathToFileURL } from "node:url";
6
6
 
7
7
  import { z } from "zod";
8
8
 
9
+ import {
10
+ formatPromptAssetIssues,
11
+ PROMPT_ASSET_SYNC_REMEDIATION,
12
+ verifyPromptAssets,
13
+ } from "../src/cli/prompt-assets.js";
9
14
  import type { ProviderDefinition } from "../src/index.js";
10
15
  import { lintProvider, type ProviderLintMode } from "../src/lint.js";
11
16
  import { safeParseSchemaSync } from "../src/schema.js";
@@ -61,7 +66,7 @@ function normalizeArgs(argv: string[]): string[] {
61
66
  return argv[0] === "check" ? argv.slice(1) : argv;
62
67
  }
63
68
 
64
- function resolveProviderRoot(inputPath: string): string {
69
+ export function resolveProviderRoot(inputPath: string): string {
65
70
  const resolvedInput = resolveFromParents(inputPath);
66
71
 
67
72
  if (!existsSync(resolvedInput)) {
@@ -129,9 +134,26 @@ export async function runChecks(
129
134
  checkProviderMetadata(provider),
130
135
  checkDockerfile(dockerfilePath),
131
136
  checkPackageJson(packageJsonPath),
137
+ checkPromptAssets(providerRoot),
132
138
  ];
133
139
  }
134
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
+
135
157
  function isScannableProviderSourceFile(relativePath: string): boolean {
136
158
  return (
137
159
  /\.(?:ts|tsx|js|jsx|mjs|cjs|sh|bash)$/.test(relativePath) ||
@@ -142,6 +164,9 @@ function isScannableProviderSourceFile(relativePath: string): boolean {
142
164
 
143
165
  function collectProviderSourceFiles(providerRoot: string): Record<string, string> {
144
166
  const sources: Record<string, string> = {};
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.
145
170
  const skipDirectories = new Set([".git", "node_modules", "dist", "build", ".next"]);
146
171
  const visit = (directory: string) => {
147
172
  for (const entry of readdirSync(directory, { withFileTypes: true })) {
@@ -48,6 +48,13 @@ const requiredPaths = [
48
48
  "src/cli/templates/provider/operations/ping.ts.tpl",
49
49
  "src/cli/templates/provider/schemas/ping.ts.tpl",
50
50
  "src/cli/templates/provider/upstream/README.md.tpl",
51
+ "src/cli/templates/provider/AGENTS.md.tpl",
52
+ "src/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl",
53
+ "src/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl",
54
+ "src/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl",
55
+ "src/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl",
56
+ "src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl",
57
+ "src/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl",
51
58
  "dist/cli/templates/provider/.dockerignore.tpl",
52
59
  "dist/cli/templates/provider/.gitignore.tpl",
53
60
  "dist/cli/templates/provider/Dockerfile.tpl",
@@ -60,6 +67,13 @@ const requiredPaths = [
60
67
  "dist/cli/templates/provider/operations/ping.ts.tpl",
61
68
  "dist/cli/templates/provider/schemas/ping.ts.tpl",
62
69
  "dist/cli/templates/provider/upstream/README.md.tpl",
70
+ "dist/cli/templates/provider/AGENTS.md.tpl",
71
+ "dist/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl",
72
+ "dist/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl",
73
+ "dist/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl",
74
+ "dist/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl",
75
+ "dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl",
76
+ "dist/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl",
63
77
  "dist/auth-turn/index.js",
64
78
  "dist/auth-turn/index.d.ts",
65
79
  "dist/auth-turn/auth-turn.v1.schema.json",