@apifuse/provider-sdk 2.2.0-beta.1 → 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.
Files changed (203) hide show
  1. package/AUTHORING.md +106 -0
  2. package/CHANGELOG.md +37 -0
  3. package/README.md +5 -1
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +14 -0
  9. package/bin/apifuse-pack-smoke.ts +22 -81
  10. package/bin/apifuse-pack-types.ts +266 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +53 -70
  13. package/bin/apifuse-submit-check.ts +510 -45
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth.d.ts +2 -2
  21. package/dist/auth.js +9 -18
  22. package/dist/ceremonies/index.d.ts +1 -1
  23. package/dist/ceremonies/index.js +8 -18
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/config/loader.d.ts +149 -8
  32. package/dist/config/loader.js +378 -89
  33. package/dist/contract-serialization.d.ts +2 -2
  34. package/dist/contract-serialization.js +3 -6
  35. package/dist/contract-types.d.ts +2 -2
  36. package/dist/contract.d.ts +3 -3
  37. package/dist/contract.js +4 -6
  38. package/dist/define.d.ts +8 -1
  39. package/dist/define.js +119 -122
  40. package/dist/dev.d.ts +1 -1
  41. package/dist/dev.js +1 -1
  42. package/dist/errors.d.ts +4 -1
  43. package/dist/errors.js +48 -0
  44. package/dist/i18n/catalog.d.ts +2 -2
  45. package/dist/i18n/catalog.js +4 -10
  46. package/dist/i18n/index.d.ts +2 -2
  47. package/dist/i18n/index.js +2 -2
  48. package/dist/i18n/keys.d.ts +2 -2
  49. package/dist/index.d.ts +44 -42
  50. package/dist/index.js +39 -37
  51. package/dist/lint.js +8 -15
  52. package/dist/provider.d.ts +11 -11
  53. package/dist/provider.js +9 -9
  54. package/dist/public-schema-field-lint.d.ts +1 -1
  55. package/dist/recipes/gov-api.js +1 -1
  56. package/dist/runtime/auth-flow.d.ts +1 -1
  57. package/dist/runtime/auth-flow.js +3 -3
  58. package/dist/runtime/browser.d.ts +1 -1
  59. package/dist/runtime/browser.js +15 -29
  60. package/dist/runtime/cache.d.ts +1 -1
  61. package/dist/runtime/cache.js +4 -8
  62. package/dist/runtime/choice.d.ts +1 -1
  63. package/dist/runtime/choice.js +31 -35
  64. package/dist/runtime/credential.d.ts +1 -1
  65. package/dist/runtime/credential.js +1 -1
  66. package/dist/runtime/env.d.ts +1 -1
  67. package/dist/runtime/executor.d.ts +1 -1
  68. package/dist/runtime/executor.js +15 -3
  69. package/dist/runtime/http.d.ts +2 -2
  70. package/dist/runtime/http.js +144 -348
  71. package/dist/runtime/insights.d.ts +1 -1
  72. package/dist/runtime/insights.js +6 -13
  73. package/dist/runtime/instrumentation.d.ts +2 -2
  74. package/dist/runtime/instrumentation.js +56 -19
  75. package/dist/runtime/keyring.js +1 -1
  76. package/dist/runtime/namespace.js +1 -1
  77. package/dist/runtime/otlp.d.ts +1 -1
  78. package/dist/runtime/perf.d.ts +1 -1
  79. package/dist/runtime/provider.d.ts +1 -1
  80. package/dist/runtime/provider.js +1 -2
  81. package/dist/runtime/proxy-errors.d.ts +1 -1
  82. package/dist/runtime/proxy-errors.js +9 -7
  83. package/dist/runtime/proxy-nodemaven.d.ts +35 -0
  84. package/dist/runtime/proxy-nodemaven.js +128 -0
  85. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  86. package/dist/runtime/proxy-retry-policy.js +326 -0
  87. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  88. package/dist/runtime/proxy-telemetry.js +55 -52
  89. package/dist/runtime/redis.d.ts +1 -1
  90. package/dist/runtime/redis.js +2 -4
  91. package/dist/runtime/request-options.d.ts +1 -1
  92. package/dist/runtime/secrets.d.ts +27 -0
  93. package/dist/runtime/secrets.js +51 -0
  94. package/dist/runtime/state.d.ts +2 -2
  95. package/dist/runtime/state.js +15 -4
  96. package/dist/runtime/stealth.d.ts +2 -2
  97. package/dist/runtime/stealth.js +108 -212
  98. package/dist/runtime/stt.d.ts +1 -1
  99. package/dist/runtime/stt.js +11 -15
  100. package/dist/runtime/trace.d.ts +2 -2
  101. package/dist/runtime/trace.js +2 -4
  102. package/dist/runtime/waterfall.d.ts +1 -1
  103. package/dist/schema.d.ts +1 -1
  104. package/dist/schema.js +7 -15
  105. package/dist/serve.d.ts +1 -1
  106. package/dist/serve.js +1 -1
  107. package/dist/server/index.d.ts +7 -7
  108. package/dist/server/index.js +6 -6
  109. package/dist/server/self-test-redaction.d.ts +1 -1
  110. package/dist/server/self-test-redaction.js +1 -1
  111. package/dist/server/self-test.d.ts +104 -3
  112. package/dist/server/self-test.js +673 -115
  113. package/dist/server/serve.d.ts +8 -3
  114. package/dist/server/serve.js +105 -59
  115. package/dist/server/types.d.ts +10 -9
  116. package/dist/server/types.js +3 -7
  117. package/dist/stealth/profiles.d.ts +1 -1
  118. package/dist/stealth/profiles.js +5 -14
  119. package/dist/stream.d.ts +1 -1
  120. package/dist/testing/index.d.ts +2 -2
  121. package/dist/testing/index.js +2 -2
  122. package/dist/testing/run.d.ts +1 -1
  123. package/dist/testing/run.js +7 -14
  124. package/dist/types.d.ts +87 -1
  125. package/dist/user-input.d.ts +30 -0
  126. package/dist/user-input.js +66 -0
  127. package/package.json +9 -5
  128. package/src/auth-turn/index.ts +1 -1
  129. package/src/auth.ts +28 -86
  130. package/src/ceremonies/index.ts +24 -78
  131. package/src/cli/commands.ts +10 -0
  132. package/src/cli/create.ts +42 -35
  133. package/src/cli/prompt-assets.ts +865 -0
  134. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  135. package/src/config/loader.ts +543 -208
  136. package/src/contract-serialization.ts +5 -11
  137. package/src/contract-types.ts +2 -2
  138. package/src/contract.ts +12 -28
  139. package/src/define.ts +247 -505
  140. package/src/dev.ts +4 -9
  141. package/src/errors.ts +58 -5
  142. package/src/i18n/catalog.ts +10 -32
  143. package/src/i18n/index.ts +2 -2
  144. package/src/i18n/keys.ts +5 -11
  145. package/src/index.ts +49 -42
  146. package/src/lint.ts +89 -159
  147. package/src/provider.ts +15 -11
  148. package/src/public-schema-field-lint.ts +7 -33
  149. package/src/recipes/gov-api.ts +2 -5
  150. package/src/runtime/auth-flow.ts +5 -7
  151. package/src/runtime/browser.ts +64 -187
  152. package/src/runtime/cache.ts +20 -67
  153. package/src/runtime/choice.ts +79 -132
  154. package/src/runtime/credential.ts +2 -2
  155. package/src/runtime/env.ts +1 -1
  156. package/src/runtime/executor.ts +23 -20
  157. package/src/runtime/http.ts +195 -559
  158. package/src/runtime/insights.ts +15 -53
  159. package/src/runtime/instrumentation.ts +78 -65
  160. package/src/runtime/keyring.ts +7 -19
  161. package/src/runtime/namespace.ts +2 -7
  162. package/src/runtime/otlp.ts +12 -23
  163. package/src/runtime/perf.ts +1 -1
  164. package/src/runtime/provider.ts +4 -9
  165. package/src/runtime/proxy-errors.ts +29 -42
  166. package/src/runtime/proxy-nodemaven.ts +178 -0
  167. package/src/runtime/proxy-retry-policy.ts +469 -0
  168. package/src/runtime/proxy-telemetry.ts +79 -77
  169. package/src/runtime/redis.ts +4 -12
  170. package/src/runtime/request-options.ts +4 -13
  171. package/src/runtime/secrets.ts +64 -0
  172. package/src/runtime/state.ts +41 -110
  173. package/src/runtime/stealth.ts +162 -377
  174. package/src/runtime/stt.ts +38 -94
  175. package/src/runtime/trace.ts +14 -44
  176. package/src/runtime/waterfall.ts +5 -18
  177. package/src/schema.ts +23 -84
  178. package/src/serve.ts +1 -1
  179. package/src/server/index.ts +12 -7
  180. package/src/server/self-test-redaction.ts +2 -2
  181. package/src/server/self-test.ts +857 -132
  182. package/src/server/serve.ts +172 -245
  183. package/src/server/types.ts +7 -19
  184. package/src/stealth/profiles.ts +10 -26
  185. package/src/stream.ts +8 -19
  186. package/src/testing/index.ts +2 -2
  187. package/src/testing/run.ts +19 -67
  188. package/src/types.ts +88 -1
  189. package/src/user-input.ts +118 -0
  190. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  191. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  192. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  193. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  194. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  195. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  196. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  197. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  198. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  199. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  200. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  201. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  202. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  203. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -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:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
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
+
3
39
  ## 2.2.0-beta.1
4
40
 
5
41
  - Release candidate for main commit 5056b8c89fe0fa8f10bafcd30f83bcc421d4b5c5.
@@ -35,6 +71,7 @@
35
71
  ## Unreleased
36
72
 
37
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.
38
75
 
39
76
  ## 2.1.0-beta.15
40
77
 
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,9 +6,14 @@ import { pathToFileURL } from "node:url";
6
6
 
7
7
  import { z } from "zod";
8
8
 
9
- import type { ProviderDefinition } from "../src";
10
- import { lintProvider, type ProviderLintMode } from "../src/lint";
11
- import { safeParseSchemaSync } from "../src/schema";
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
- const skipDirectories = new Set([
150
- ".git",
151
- "node_modules",
152
- "dist",
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
- { ...provider, providerSourceFiles },
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
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { main as runMain } from "../src/cli/create";
3
+ import { main as runMain } from "../src/cli/create.js";
4
4
 
5
5
  export { runMain as main };
6
6
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { existsSync } from "node:fs";
4
4
  import { dirname, relative, resolve } from "node:path";
5
- import type { ProviderDefinition } from "../src";
5
+ import type { ProviderDefinition } from "../src/index.js";
6
6
  import {
7
7
  createBrowserClient,
8
8
  createCredentialContext,
@@ -14,10 +14,10 @@ import {
14
14
  createSttClientFromEnv,
15
15
  PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
16
16
  ProviderError,
17
- } from "../src";
18
- import { createMemoryProviderRuntimeState } from "../src/runtime/state";
19
- import { createTraceContext } from "../src/runtime/trace";
20
- import type { BrowserClient, ProviderContext } from "../src/types";
17
+ } from "../src/index.js";
18
+ import { createMemoryProviderRuntimeState } from "../src/runtime/state.js";
19
+ import { createTraceContext } from "../src/runtime/trace.js";
20
+ import type { BrowserClient, ProviderContext } from "../src/types.js";
21
21
 
22
22
  const HELP_TEXT = `Usage: apifuse dev [path]
23
23
  Example: apifuse dev providers/korea-air-quality
@@ -33,12 +33,9 @@ export async function main() {
33
33
 
34
34
  const providerPath = resolveProviderPath(args[0] ?? ".");
35
35
  const providerModule = await import(resolve(providerPath, "index.ts"));
36
- const provider = assertProviderDefinition(
37
- providerModule.default,
38
- providerPath,
39
- );
36
+ const provider = assertProviderDefinition(providerModule.default, providerPath);
40
37
 
41
- const { startDevServer } = await import("../src/dev");
38
+ const { startDevServer } = await import("../src/dev.js");
42
39
  const port = Number(process.env.APIFUSE__RUNTIME__PORT) || 3900;
43
40
 
44
41
  startDevServer(provider, { port });
@@ -57,8 +54,7 @@ export async function main() {
57
54
 
58
55
  const firstOperation = Object.keys(provider.operations)[0];
59
56
  if (firstOperation) {
60
- const sampleInput =
61
- provider.operations[firstOperation]?.fixtures?.request ?? {};
57
+ const sampleInput = provider.operations[firstOperation]?.fixtures?.request ?? {};
62
58
  const sampleBody = JSON.stringify({
63
59
  requestId: `req_local_${firstOperation}`,
64
60
  input: sampleInput,
@@ -149,9 +145,7 @@ function renderHotReloadCommand(providerPath: string, port: number): string {
149
145
  const devEntry = resolve(providerPath, "dev.ts");
150
146
  if (existsSync(devEntry)) {
151
147
  const relativeDevEntry = relative(process.cwd(), devEntry) || "dev.ts";
152
- const portPrefix = process.env.APIFUSE__RUNTIME__PORT
153
- ? `APIFUSE__RUNTIME__PORT=${port} `
154
- : "";
148
+ const portPrefix = process.env.APIFUSE__RUNTIME__PORT ? `APIFUSE__RUNTIME__PORT=${port} ` : "";
155
149
  return `${portPrefix}bun --hot ${relativeDevEntry}`;
156
150
  }
157
151
  return "rerun `apifuse dev` after edits (no dev.ts entrypoint found)";
@@ -166,40 +160,28 @@ function createUnsupportedBrowserStub(): BrowserClient {
166
160
  engine: "playwright-stealth",
167
161
  async close() {},
168
162
  async newPage() {
169
- throw new ProviderError(
170
- "Browser runtime is not enabled for this provider",
171
- {
172
- code: "BROWSER_RUNTIME_UNSUPPORTED",
173
- fix: 'Set provider runtime to "browser" to use ctx.browser',
174
- },
175
- );
163
+ throw new ProviderError("Browser runtime is not enabled for this provider", {
164
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
165
+ fix: 'Set provider runtime to "browser" to use ctx.browser',
166
+ });
176
167
  },
177
168
  async rawPage() {
178
- throw new ProviderError(
179
- "Browser runtime is not enabled for this provider",
180
- {
181
- code: "BROWSER_RUNTIME_UNSUPPORTED",
182
- fix: 'Set provider runtime to "browser" and APIFUSE__CDP_POOL__URL to use ctx.browser.rawPage',
183
- },
184
- );
169
+ throw new ProviderError("Browser runtime is not enabled for this provider", {
170
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
171
+ fix: 'Set provider runtime to "browser" and APIFUSE__CDP_POOL__URL to use ctx.browser.rawPage',
172
+ });
185
173
  },
186
174
  async withIsolatedContext() {
187
- throw new ProviderError(
188
- "Browser runtime is not enabled for this provider",
189
- {
190
- code: "BROWSER_RUNTIME_UNSUPPORTED",
191
- fix: 'Set provider runtime to "browser" to use ctx.browser.withIsolatedContext',
192
- },
193
- );
175
+ throw new ProviderError("Browser runtime is not enabled for this provider", {
176
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
177
+ fix: 'Set provider runtime to "browser" to use ctx.browser.withIsolatedContext',
178
+ });
194
179
  },
195
180
  async solveChallenge() {
196
- throw new ProviderError(
197
- "Browser runtime is not enabled for this provider",
198
- {
199
- code: "BROWSER_RUNTIME_UNSUPPORTED",
200
- fix: 'Set provider runtime to "browser" to use ctx.browser.solveChallenge',
201
- },
202
- );
181
+ throw new ProviderError("Browser runtime is not enabled for this provider", {
182
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
183
+ fix: 'Set provider runtime to "browser" to use ctx.browser.solveChallenge',
184
+ });
203
185
  },
204
186
  };
205
187
  }
@@ -216,10 +198,7 @@ function createUnsupportedAuthStub() {
216
198
  };
217
199
  }
218
200
 
219
- function assertProviderDefinition(
220
- value: unknown,
221
- providerPath: string,
222
- ): ProviderDefinition {
201
+ function assertProviderDefinition(value: unknown, providerPath: string): ProviderDefinition {
223
202
  if (!isProviderDefinition(value)) {
224
203
  throw new Error(
225
204
  `Expected ${resolve(providerPath, "index.ts")} to export default defineProvider(...)`,
@@ -230,11 +209,7 @@ function assertProviderDefinition(
230
209
  }
231
210
 
232
211
  function isProviderDefinition(value: unknown): value is ProviderDefinition {
233
- if (
234
- !isRecord(value) ||
235
- !isRecord(value.meta) ||
236
- !isRecord(value.operations)
237
- ) {
212
+ if (!isRecord(value) || !isRecord(value.meta) || !isRecord(value.operations)) {
238
213
  return false;
239
214
  }
240
215
 
@@ -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",