@apifuse/provider-sdk 2.1.0-beta.2 → 2.1.0-beta.20

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 (215) hide show
  1. package/AUTHORING.md +330 -8
  2. package/CHANGELOG.md +85 -1
  3. package/README.md +64 -17
  4. package/SUBMISSION.md +86 -0
  5. package/bin/apifuse-check.ts +60 -6
  6. package/bin/apifuse-dev.ts +58 -8
  7. package/bin/apifuse-pack-check.ts +32 -2
  8. package/bin/apifuse-pack-smoke.ts +133 -6
  9. package/bin/apifuse-perf.ts +142 -49
  10. package/bin/apifuse-record.ts +182 -104
  11. package/bin/apifuse-submit-check.ts +2849 -0
  12. package/bin/apifuse.ts +1 -1
  13. package/dist/auth.d.ts +76 -0
  14. package/dist/auth.js +436 -0
  15. package/dist/ceremonies/index.d.ts +41 -0
  16. package/dist/ceremonies/index.js +490 -0
  17. package/dist/choice-token.d.ts +24 -0
  18. package/dist/choice-token.js +74 -0
  19. package/dist/cli/commands.d.ts +10 -0
  20. package/dist/cli/commands.js +80 -0
  21. package/dist/cli/create.d.ts +47 -0
  22. package/dist/cli/create.js +745 -0
  23. package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
  24. package/dist/cli/templates/provider/.gitignore.tpl +22 -0
  25. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  26. package/dist/cli/templates/provider/README.md.tpl +163 -0
  27. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  28. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  29. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  30. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  31. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  32. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  33. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  34. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  35. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  36. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  37. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  38. package/dist/config/loader.d.ts +107 -0
  39. package/dist/config/loader.js +935 -0
  40. package/dist/contract-json.d.ts +9 -0
  41. package/dist/contract-json.js +51 -0
  42. package/dist/contract-serialization.d.ts +4 -0
  43. package/dist/contract-serialization.js +78 -0
  44. package/dist/contract-types.d.ts +49 -0
  45. package/dist/contract-types.js +1 -0
  46. package/dist/contract.d.ts +6 -0
  47. package/dist/contract.js +156 -0
  48. package/dist/define.d.ts +100 -0
  49. package/dist/define.js +1383 -0
  50. package/dist/dev.d.ts +9 -0
  51. package/dist/dev.js +15 -0
  52. package/dist/errors.d.ts +59 -0
  53. package/dist/errors.js +97 -0
  54. package/dist/i18n/catalog.d.ts +29 -0
  55. package/dist/i18n/catalog.js +159 -0
  56. package/dist/i18n/index.d.ts +2 -0
  57. package/dist/i18n/index.js +2 -0
  58. package/dist/i18n/keys.d.ts +10 -0
  59. package/dist/i18n/keys.js +34 -0
  60. package/dist/index.d.ts +42 -0
  61. package/dist/index.js +38 -0
  62. package/dist/lint.d.ts +74 -0
  63. package/dist/lint.js +729 -0
  64. package/dist/observability.d.ts +5 -0
  65. package/dist/observability.js +39 -0
  66. package/dist/provider.d.ts +11 -0
  67. package/dist/provider.js +9 -0
  68. package/dist/public-schema-field-lint.d.ts +2 -0
  69. package/dist/public-schema-field-lint.js +158 -0
  70. package/dist/recipes/gov-api.d.ts +19 -0
  71. package/dist/recipes/gov-api.js +72 -0
  72. package/dist/recipes/rest-api.d.ts +21 -0
  73. package/dist/recipes/rest-api.js +115 -0
  74. package/dist/runtime/auth-flow.d.ts +14 -0
  75. package/dist/runtime/auth-flow.js +46 -0
  76. package/dist/runtime/browser.d.ts +25 -0
  77. package/dist/runtime/browser.js +1237 -0
  78. package/dist/runtime/cache.d.ts +10 -0
  79. package/dist/runtime/cache.js +372 -0
  80. package/dist/runtime/choice.d.ts +15 -0
  81. package/dist/runtime/choice.js +435 -0
  82. package/dist/runtime/credential.d.ts +8 -0
  83. package/dist/runtime/credential.js +61 -0
  84. package/dist/runtime/env.d.ts +2 -0
  85. package/dist/runtime/env.js +10 -0
  86. package/dist/runtime/executor.d.ts +16 -0
  87. package/dist/runtime/executor.js +51 -0
  88. package/dist/runtime/http.d.ts +8 -0
  89. package/dist/runtime/http.js +726 -0
  90. package/dist/runtime/insights.d.ts +9 -0
  91. package/dist/runtime/insights.js +324 -0
  92. package/dist/runtime/instrumentation.d.ts +8 -0
  93. package/dist/runtime/instrumentation.js +269 -0
  94. package/dist/runtime/key-derivation.d.ts +24 -0
  95. package/dist/runtime/key-derivation.js +73 -0
  96. package/dist/runtime/keyring.d.ts +25 -0
  97. package/dist/runtime/keyring.js +93 -0
  98. package/dist/runtime/namespace.d.ts +9 -0
  99. package/dist/runtime/namespace.js +19 -0
  100. package/dist/runtime/otlp.d.ts +39 -0
  101. package/dist/runtime/otlp.js +103 -0
  102. package/dist/runtime/perf.d.ts +12 -0
  103. package/dist/runtime/perf.js +52 -0
  104. package/dist/runtime/prevalidate.d.ts +12 -0
  105. package/dist/runtime/prevalidate.js +173 -0
  106. package/dist/runtime/provider.d.ts +2 -0
  107. package/dist/runtime/provider.js +11 -0
  108. package/dist/runtime/proxy-errors.d.ts +21 -0
  109. package/dist/runtime/proxy-errors.js +83 -0
  110. package/dist/runtime/proxy-telemetry.d.ts +8 -0
  111. package/dist/runtime/proxy-telemetry.js +174 -0
  112. package/dist/runtime/redis.d.ts +17 -0
  113. package/dist/runtime/redis.js +82 -0
  114. package/dist/runtime/request-options.d.ts +3 -0
  115. package/dist/runtime/request-options.js +42 -0
  116. package/dist/runtime/state.d.ts +17 -0
  117. package/dist/runtime/state.js +344 -0
  118. package/dist/runtime/stealth.d.ts +21 -0
  119. package/dist/runtime/stealth.js +980 -0
  120. package/dist/runtime/stt.d.ts +22 -0
  121. package/dist/runtime/stt.js +480 -0
  122. package/dist/runtime/trace.d.ts +26 -0
  123. package/dist/runtime/trace.js +142 -0
  124. package/dist/runtime/waterfall.d.ts +12 -0
  125. package/dist/runtime/waterfall.js +147 -0
  126. package/dist/schema.d.ts +74 -0
  127. package/dist/schema.js +243 -0
  128. package/dist/serve.d.ts +1 -0
  129. package/dist/serve.js +1 -0
  130. package/dist/server/index.d.ts +3 -0
  131. package/dist/server/index.js +2 -0
  132. package/dist/server/serve.d.ts +64 -0
  133. package/dist/server/serve.js +1118 -0
  134. package/dist/server/types.d.ts +136 -0
  135. package/dist/server/types.js +86 -0
  136. package/dist/stealth/profiles.d.ts +4 -0
  137. package/dist/stealth/profiles.js +259 -0
  138. package/dist/stream.d.ts +44 -0
  139. package/dist/stream.js +151 -0
  140. package/dist/testing/helpers.d.ts +23 -0
  141. package/dist/testing/helpers.js +95 -0
  142. package/dist/testing/index.d.ts +2 -0
  143. package/dist/testing/index.js +2 -0
  144. package/dist/testing/run.d.ts +34 -0
  145. package/dist/testing/run.js +307 -0
  146. package/dist/types.d.ts +1467 -0
  147. package/dist/types.js +61 -0
  148. package/dist/utils/date.d.ts +6 -0
  149. package/dist/utils/date.js +101 -0
  150. package/dist/utils/parse.d.ts +16 -0
  151. package/dist/utils/parse.js +51 -0
  152. package/dist/utils/text.d.ts +4 -0
  153. package/dist/utils/text.js +14 -0
  154. package/dist/utils/transform.d.ts +8 -0
  155. package/dist/utils/transform.js +48 -0
  156. package/package.json +56 -29
  157. package/src/auth.ts +786 -0
  158. package/src/ceremonies/index.ts +8 -2
  159. package/src/choice-token.ts +165 -0
  160. package/src/cli/commands.ts +34 -11
  161. package/src/cli/create.ts +222 -128
  162. package/src/cli/templates/provider/.dockerignore.tpl +22 -0
  163. package/src/cli/templates/provider/.gitignore.tpl +22 -0
  164. package/src/cli/templates/provider/README.md.tpl +87 -7
  165. package/src/cli/templates/provider/dev.ts.tpl +1 -1
  166. package/src/cli/templates/provider/domain/README.md.tpl +3 -0
  167. package/src/cli/templates/provider/index.ts.tpl +5 -47
  168. package/src/cli/templates/provider/mappers/README.md.tpl +3 -0
  169. package/src/cli/templates/provider/meta.ts.tpl +7 -0
  170. package/src/cli/templates/provider/operations/index.ts.tpl +5 -0
  171. package/src/cli/templates/provider/operations/ping.ts.tpl +24 -0
  172. package/src/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  173. package/src/cli/templates/provider/start.ts.tpl +1 -1
  174. package/src/cli/templates/provider/upstream/README.md.tpl +3 -0
  175. package/src/config/loader.ts +1224 -9
  176. package/src/contract-json.ts +75 -0
  177. package/src/contract-serialization.ts +89 -0
  178. package/src/contract-types.ts +52 -0
  179. package/src/contract.ts +216 -0
  180. package/src/define.ts +1820 -70
  181. package/src/errors.ts +27 -0
  182. package/src/i18n/catalog.ts +277 -0
  183. package/src/i18n/index.ts +2 -0
  184. package/src/i18n/keys.ts +64 -0
  185. package/src/index.ts +189 -9
  186. package/src/lint.ts +580 -73
  187. package/src/observability.ts +41 -0
  188. package/src/provider.ts +131 -4
  189. package/src/public-schema-field-lint.ts +237 -0
  190. package/src/runtime/auth-flow.ts +9 -0
  191. package/src/runtime/browser.ts +1054 -51
  192. package/src/runtime/cache.ts +528 -0
  193. package/src/runtime/choice.ts +760 -0
  194. package/src/runtime/executor.ts +32 -3
  195. package/src/runtime/http.ts +980 -195
  196. package/src/runtime/insights.ts +11 -11
  197. package/src/runtime/instrumentation.ts +12 -4
  198. package/src/runtime/key-derivation.ts +1 -1
  199. package/src/runtime/keyring.ts +4 -3
  200. package/src/runtime/proxy-errors.ts +132 -0
  201. package/src/runtime/proxy-telemetry.ts +253 -0
  202. package/src/runtime/redis.ts +116 -0
  203. package/src/runtime/request-options.ts +66 -0
  204. package/src/runtime/state.ts +563 -0
  205. package/src/runtime/stealth.ts +1336 -0
  206. package/src/runtime/stt.ts +629 -0
  207. package/src/runtime/trace.ts +1 -1
  208. package/src/schema.ts +363 -1
  209. package/src/server/serve.ts +1192 -75
  210. package/src/server/types.ts +37 -0
  211. package/src/stream.ts +210 -0
  212. package/src/testing/run.ts +40 -6
  213. package/src/types.ts +1283 -59
  214. package/src/runtime/tls.ts +0 -434
  215. package/src/types/playwright-stealth.d.ts +0 -9
package/AUTHORING.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ## Generator and runtime alignment
2
2
 
3
3
  - Canonical scaffolding command: `apifuse create`
4
- - Monorepo contributors should use `apifuse create <name> --preset monorepo`
4
+ - External bounty workspaces are one-provider repositories initialized with the standalone create flow. `--preset monorepo` is an internal APIFuse maintainer path only and must reject outside the private monorepo detected by `packages/provider-sdk/package.json`.
5
5
  - Standalone bounty contributors should use `bunx @apifuse/provider-sdk@beta create <name> --yes` until this release is promoted to `latest`
6
6
  - Provider server contract is:
7
7
  - dev default `3900`
@@ -47,12 +47,36 @@ description:
47
47
  - `description` — 150+ chars English (error-level rule)
48
48
  - Every Zod field in input AND output has `.describe()` including nested objects + array items (error-level rule)
49
49
  - `fixtures.request` + `fixtures.response` both present (error-level rule)
50
- - Exactly one of `healthCheck` or `healthCheckUnsupported` per operation. Prefer `healthCheck` for safe read-only upstream probes; use `healthCheckUnsupported` only with a specific reason for destructive, paid, credential-sensitive, flaky, or otherwise unsafe probes.
50
+ - Exactly one of `healthCheck`, `healthCheckUnsupported`, or `healthJourneys[].coversOperations` coverage per operation. Prefer `healthCheck` for safe read-only upstream probes; use `healthCheckUnsupported` only with a specific reason for destructive, paid, credential-sensitive, flaky, or otherwise unsafe probes. Use a provider-level health journey when a destructive or credential-sensitive flow can be proven safely only as a multi-step boundary test, such as stopping at a payment WebView URL.
51
51
 
52
52
  ### Factored operations
53
53
 
54
54
  Use `defineOperation()` when an operation is large enough to live beside helper functions or in a separate module. It preserves the same type inference as inline `defineProvider()` operations and can be placed directly in the provider `operations` map. `defineProvider()` accepts Zod and Standard Schema v1-compatible schemas. If config validation fails, the SDK names the field to fix, for example `runtime`, `auth.mode`, `operations.<id>.handler`, or `operations.<id>.fixtures.response`.
55
55
 
56
+ ### Health assertion context
57
+
58
+ `healthCheck.cases[].assertions` receives a `HealthCheckAssertionContext` with
59
+ `data`, `status`, `durationMs`, and optional `meta`. `data` is typed from the
60
+ operation output schema, so assertions should inspect normalized output instead
61
+ of reaching into transport internals.
62
+
63
+ <!-- @magic-start:sample -->
64
+ ```ts
65
+ healthCheck: {
66
+ interval: "5m",
67
+ cases: [{
68
+ name: "lookup baseline",
69
+ input: { q: "btc" },
70
+ assertions: ({ data, status, durationMs }) => {
71
+ if (status !== 200 || data.results.length === 0 || durationMs > 3000) {
72
+ return { status: "degraded", label: "lookup baseline changed" };
73
+ }
74
+ },
75
+ }],
76
+ }
77
+ ```
78
+ <!-- @magic-end:sample -->
79
+
56
80
  ### Strongly recommended (warn-level rules)
57
81
 
58
82
  - `description` includes "use" AND "when" phrasing
@@ -65,6 +89,158 @@ Use `defineOperation()` when an operation is large enough to live beside helper
65
89
  - `tags`: operation-level semantic tags for retrieval (e.g., `["weather", "korea", "realtime"]`)
66
90
  - `relatedOperations`: `{ alternatives?: string[] }` — links to fallback/sibling operations
67
91
 
92
+ ### STT runtime capability for audio OTP and short transcription
93
+
94
+ Providers that need speech-to-text should use the SDK runtime capability instead
95
+ of constructing a vendor client inside provider code. Declare STT at the provider
96
+ level, then call `ctx.stt` from operation handlers or auth-flow handlers.
97
+
98
+ <!-- @magic-start:sample -->
99
+ ```ts
100
+ export default defineProvider({
101
+ id: "example-provider",
102
+ // ...metadata, auth, operations, allowedHosts
103
+ stt: { mode: "required" },
104
+ operations: {
105
+ verifyAudioOtp: {
106
+ input: z.object({
107
+ audioBase64: z.string().describe("Base64-encoded short OTP audio"),
108
+ mediaType: z.string().optional().describe("Audio MIME type"),
109
+ }),
110
+ output: z.object({ code: z.string().describe("Verification code") }),
111
+ async handler(ctx, input) {
112
+ const transcript = await ctx.stt.transcribe({
113
+ audio: {
114
+ kind: "base64",
115
+ data: input.audioBase64,
116
+ mediaType: input.mediaType,
117
+ },
118
+ language: "ko-KR",
119
+ mode: "otp",
120
+ verificationCode: { codeLengths: [4, 6] },
121
+ });
122
+
123
+ const code =
124
+ transcript.verificationCode?.code ??
125
+ ctx.stt.extractVerificationCode(transcript.text, {
126
+ locale: "ko-KR",
127
+ codeLengths: [4, 6],
128
+ }).code;
129
+
130
+ return { code };
131
+ },
132
+ healthCheckUnsupported: {
133
+ reason: "Audio OTP transcription is cost-bearing and requires explicit smoke evidence.",
134
+ },
135
+ },
136
+ },
137
+ });
138
+ ```
139
+ <!-- @magic-end:sample -->
140
+
141
+ Best-practice rules:
142
+
143
+ - `stt: { mode: "required" }` is the production path for providers that depend
144
+ on STT; APIFuse provider manifests project STT credentials, model config, and
145
+ Cloudflare egress only for required STT. Use `mode: "optional"` only when STT
146
+ is a host/test override or truly best-effort capability that can remain
147
+ unavailable in production.
148
+ - Do not assume OTPs are always four digits. Configure accepted lengths, for
149
+ example `[4, 6]`, and keep the returned code as a string to preserve leading
150
+ zeros.
151
+ - Prompts are hints, not correctness guarantees. General transcription sends no
152
+ prompt by default. OTP mode may send a default digit-preserving hint. Use a
153
+ custom `initialPrompt` only with `promptPolicy: "custom-hint"`, and do not log
154
+ prompts, transcripts, raw audio, or OTP values.
155
+ - STT v1 accepts JSON-safe base64 audio only. Do not fetch arbitrary audio URLs
156
+ from provider code; URL input needs separate SSRF/private-network policy.
157
+ - Local and production wiring use the same env-backed runtime path. For the
158
+ Cloudflare Workers AI backend, set `APIFUSE__STT__BACKEND=cloudflare-workers-ai`,
159
+ `APIFUSE__STT__MODEL=@cf/openai/whisper-large-v3-turbo`,
160
+ `APIFUSE__CLOUDFLARE__ACCOUNT_ID`, and `APIFUSE__STT__CLOUDFLARE_API_TOKEN` in `.env.local` or the
161
+ provider workload environment. Do not deploy a Cloudflare Worker proxy for the
162
+ MVP; the SDK runtime calls Workers AI REST directly.
163
+ - Submission checks and health checks must not invoke live STT by default.
164
+ Provide explicit smoke evidence when a provider depends on audio OTP behavior.
165
+
166
+
167
+ ### Health journey DX for SMS/payment flows
168
+
169
+ Use `defineSmsOtpMatcher()` plus `defineHealthJourney()` when a real health signal requires an OTP ceremony and a safe handoff boundary. Keep matcher fields standards-backed: ISO 3166-1 alpha-2 `country`, BCP 47 `locale`, E.164 `phoneNumber` when present, ISO 8601 durations, and `nationalServiceCode` origins for local service senders. Do not add custom allowlist fields such as `senderAllowlist`; model the sender as an origin instead.
170
+
171
+ <!-- @magic-start:sample -->
172
+ ```ts
173
+ import {
174
+ defineHealthJourney,
175
+ defineProvider,
176
+ defineSmsOtpMatcher,
177
+ every,
178
+ } from "@apifuse/provider-sdk";
179
+
180
+ const phoneOtp = defineSmsOtpMatcher({
181
+ id: "phone-otp",
182
+ country: "KR",
183
+ locale: "ko-KR",
184
+ origins: [
185
+ {
186
+ kind: "nationalServiceCode",
187
+ country: "KR",
188
+ value: "16615270",
189
+ display: "1661-5270",
190
+ },
191
+ ],
192
+ code: { pattern: /인증번호는\s*\[([0-9]{4})\]/, capture: 1 },
193
+ maxAge: "PT5M",
194
+ waitTimeout: "PT2M30S",
195
+ clockSkew: "PT10S",
196
+ });
197
+
198
+ const paymentWebviewJourney = defineHealthJourney({
199
+ id: "sms-payment-webview",
200
+ schedule: every("8h", { jitter: "PT20M" }),
201
+ timeout: "PT5M",
202
+ cooldown: "PT8H",
203
+ requiredSecrets: [
204
+ "APIFUSE__HEALTH_MONITOR__PROVIDER_PHONE",
205
+ "APIFUSE__HEALTH_MONITOR__PROVIDER_PASSWORD",
206
+ "APIFUSE__HEALTH_MONITOR__PROVIDER_CANARY_ORDER_JSON",
207
+ ],
208
+ coversOperations: ["verify-phone", "confirm-phone", "place-order"],
209
+ smsMatchers: [phoneOtp],
210
+ steps: [
211
+ { id: "send-phone-otp", kind: "operation", operationId: "verify-phone" },
212
+ { id: "wait-phone-otp", kind: "smsOtp", usesSmsMatcher: "phone-otp" },
213
+ { id: "confirm-phone-otp", kind: "operation", operationId: "confirm-phone" },
214
+ {
215
+ id: "create-payment-webview",
216
+ kind: "operation",
217
+ operationId: "place-order",
218
+ safeBoundary: "paymentWebviewUrl",
219
+ },
220
+ ],
221
+ });
222
+
223
+ export default defineProvider({
224
+ id: "example-provider",
225
+ // ...metadata, auth, operations, allowedHosts
226
+ healthJourneys: [paymentWebviewJourney],
227
+ });
228
+ ```
229
+ <!-- @magic-end:sample -->
230
+
231
+ The journey runner supplies `ctx.gateway`, `ctx.sms.waitForOtp()`, `ctx.journal.sideEffect()`, `ctx.state`, and `ctx.event.operation()` to the optional journey `run` function. Provider authors should keep `run` small: call the covered operations in step order, stop at the declared safe boundary, and let the generated health metadata carry schedule, timeout, required secret, and SMS matcher information to the health monitor.
232
+
233
+ For authenticated journeys, open a fresh connection inside `run` with `ctx.gateway.connect({ input: { ... } })`, execute covered operations with the returned `connectionId`, and disconnect in a `finally` block. Do not require or store long-lived `HEALTH_MONITOR_*_CONNECTION_ID` secrets; those stale connection IDs can hide broken login ceremonies.
234
+
235
+ Use the runtime capabilities narrowly:
236
+
237
+ - `ctx.gateway.execute()` is the default path for operation health evidence; the runner records operation success/failure automatically.
238
+ - `ctx.journal.sideEffect()` wraps non-replayable provider mutations such as create/cancel/send operations.
239
+ - `ctx.state.namespace(name, policy)` stores bounded lifecycle memory and recovery cursors with TTL/quota/value-size policy. It is not a replacement for the side-effect journal.
240
+ - `ctx.event.operation()` records only synthetic operation outcomes proven by the journey, such as recovery/manual-review checks that are not direct gateway calls. The runtime rejects events for operations outside `coversOperations`.
241
+
242
+ Do not import `apps/health-monitor`, generated health artifacts, database repositories, schedulers, or recorders from provider code. If a journey needs provider-specific helper code, place it under the provider package (for example `providers/<id>/health-journeys/*`) and keep the SDK boundary generic.
243
+
68
244
  ### External bounty submission evidence
69
245
 
70
246
  External contributors are expected to submit standalone Provider source plus:
@@ -74,6 +250,7 @@ External contributors are expected to submit standalone Provider source plus:
74
250
  - Health coverage table for every Operation.
75
251
  - `bun run check` output.
76
252
  - `bun run test` output.
253
+ - `bun run submit-check` score/verdict and generated `submission-report.md`.
77
254
  - Fixture evidence and known upstream constraints.
78
255
 
79
256
  Maintainers own monorepo import under `providers/<id>/`, registry generation,
@@ -87,15 +264,160 @@ deployment projection checks, and release workflows.
87
264
  - Credential-backed smoke requests pass local-only credential material in
88
265
  `connection.secrets`. Keep real values in shell env or `.env`, never in source
89
266
  or fixtures.
267
+ - Hand-written auth flows should use `ctx.auth` helpers and return exactly one
268
+ terminal/next turn from each handler: `ctx.auth.nextForm(...)` or
269
+ `ctx.auth.nextPoll(...)` to ask Gateway for the next user/system step,
270
+ `ctx.auth.complete(...)` to finish with `data.credential`, or
271
+ `ctx.auth.abort(...)` to stop safely. Keep abort `data` and `actionHint`
272
+ JSON-safe and secret-free; never include raw cookies, credentials, headers,
273
+ HTML, or upstream `Error` objects.
274
+
275
+ ```ts
276
+ export default defineProvider({
277
+ id: "example-provider",
278
+ version: "1.0.0",
279
+ runtime: "standard",
280
+ auth: {
281
+ mode: "credentials",
282
+ flow: {
283
+ async start(ctx) {
284
+ return ctx.auth.nextForm({
285
+ fields: {
286
+ email: { type: "email", labelKey: "auth.email.label" },
287
+ password: { type: "password", labelKey: "auth.password.label" },
288
+ },
289
+ hintKey: "auth.signIn",
290
+ });
291
+ },
292
+ async continue(ctx, input) {
293
+ const result = await loginWithSubmittedFields(ctx, input);
294
+ if (result.blocked) {
295
+ return ctx.auth.abort({
296
+ code: "account_action_required",
297
+ retry: "after_user_action",
298
+ actionHint: { kind: "open_provider_app" },
299
+ message: "Approve the login in the provider app.",
300
+ });
301
+ }
302
+ return ctx.auth.complete({
303
+ credential: { cookie: result.cookie },
304
+ metadata: { accountId: result.accountId },
305
+ });
306
+ },
307
+ },
308
+ },
309
+ credential: { keys: ["cookie"] },
310
+ // ...metadata and operations
311
+ });
312
+ ```
313
+
314
+ - Credentials auth providers should use `defineCredentialsAuth()` instead of
315
+ hand-writing `auth.flow.start/continue`. The helper exposes one happy path:
316
+ declare form `fields`, declare `credentialKeys`, and put upstream login/session
317
+ creation in `login(ctx, input)`. It returns both `auth` and `credential` for
318
+ `defineProvider()` and builds the complete turn as `data.credential`, which is
319
+ the only value Gateway persists onto the connection.
320
+
321
+ ```ts
322
+ import { defineCredentialsAuth, defineProvider } from "@apifuse/provider-sdk";
323
+
324
+ const credentialsAuth = defineCredentialsAuth({
325
+ fields: {
326
+ email: { type: "email", labelKey: "auth.email.label" },
327
+ password: { type: "password", labelKey: "auth.password.label" },
328
+ },
329
+ credentialKeys: ["cookie"] as const,
330
+ storesReusableSecret: true,
331
+ justification: "Session cookie is required for authenticated operations.",
332
+ async login(ctx, input) {
333
+ const cookie = await loginAndBuildSessionCookie(ctx, input);
334
+ return { credential: { cookie } };
335
+ },
336
+ });
337
+
338
+ export default defineProvider({
339
+ id: "example-provider",
340
+ version: "1.0.0",
341
+ runtime: "standard",
342
+ auth: credentialsAuth.auth,
343
+ credential: credentialsAuth.credential,
344
+ context: credentialsAuth.context,
345
+ // ...metadata and operations
346
+ });
347
+ ```
348
+
349
+ For OTP, MFA, CAPTCHA handoff, or user-approved login, return a challenge from
350
+ `login()` instead of hand-writing `contextPatch`, `poll`, and final credential
351
+ turns. SDK stores the pending challenge in auth-flow context, returns the next
352
+ form/pending turn, and still persists only the final `data.credential`.
353
+
354
+ ```ts
355
+ import {
356
+ credentialsAuthChallenge,
357
+ defineCredentialsAuth,
358
+ } from "@apifuse/provider-sdk";
359
+
360
+ const credentialsAuth = defineCredentialsAuth({
361
+ fields: {
362
+ email: { type: "email" },
363
+ password: { type: "password" },
364
+ },
365
+ credentialKeys: ["cookie"] as const,
366
+ async login(ctx, input) {
367
+ const result = await passwordLogin(ctx, input);
368
+ if (result.otpRequired) {
369
+ return credentialsAuthChallenge("otp", {
370
+ state: { transactionId: result.transactionId },
371
+ hintKey: "auth.otp.prompt",
372
+ });
373
+ }
374
+ if (result.manualApprovalRequired) {
375
+ return credentialsAuthChallenge("manualApproval", {
376
+ state: { transactionId: result.transactionId },
377
+ hintKey: "auth.manualApproval.openApp",
378
+ timing: { suggestedPollIntervalMs: 3000, maxWaitMs: 120000 },
379
+ });
380
+ }
381
+ return { credential: { cookie: result.cookie } };
382
+ },
383
+ challenges: {
384
+ otp: {
385
+ fields: { otp: { type: "otp", labelKey: "auth.otp.label" } },
386
+ async verify(ctx, input, state) {
387
+ const result = await verifyOtp(ctx, state.transactionId, input.otp);
388
+ return { credential: { cookie: result.cookie } };
389
+ },
390
+ },
391
+ manualApproval: {
392
+ async poll(ctx, state) {
393
+ const result = await checkApproval(ctx, state.transactionId);
394
+ if (!result.approved) return null;
395
+ return { credential: { cookie: result.cookie } };
396
+ },
397
+ },
398
+ },
399
+ });
400
+ ```
90
401
  - Auth-flow debugging starts with `/auth/start`, continues with
91
402
  `/auth/continue`, and carries returned `contextPatch` values into the next
92
403
  request's `context`.
93
- - TLS/browser providers may require local runtime setup outside Provider code:
94
- use `bun pm untrusted`/`bun pm trust koffi` for blocked native TLS dependency
95
- scripts, `browser.engine: "playwright-stealth"` for TypeScript browser
96
- Providers (`nodriver` is Python-runtime only), `bunx playwright install
97
- chromium` for local Playwright browser assets, or
98
- `CDP_POOL_URL`/`APIFUSE_CDP_POOL_URL` for remote browser debugging.
404
+ - Stealth/browser providers may require local runtime setup outside Provider code:
405
+ keep access-sensitive operations on `ctx.stealth.fetch()` with an SDK stealth
406
+ `profile`; the TypeScript runtime uses `impit` behind that interface, so do
407
+ not add per-operation JA3, HTTP/2 SETTINGS, or pseudo-header tuning. `ctx.stealth`
408
+ supports Chrome/Firefox-style profiles; use `browser.engine:
409
+ "playwright-stealth"` for Safari-specific or real browser Providers
410
+ (`nodriver` is Python-runtime only); install local browser assets with
411
+ `bunx playwright install chromium`, or set
412
+ `APIFUSE__CDP_POOL__URL` for remote browser debugging.
413
+
414
+ ### Running the pre-submission report
415
+
416
+ ```bash
417
+ bun run submit-check
418
+ ```
419
+
420
+ The report scores review readiness across definition metadata, operation/schema quality, fixtures/tests, health coverage, local smoke evidence, auth safety, secret hygiene, and submission docs. It is not a payout guarantee; any blocker must be fixed before review. For the complete public-only submission checklist, see `SUBMISSION.md` in the SDK package.
99
421
 
100
422
  ### Running the lint locally
101
423
 
package/CHANGELOG.md CHANGED
@@ -1,11 +1,95 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.1.0-beta.20
4
+
5
+ - Release candidate for main commit 2a07cc5aef0d517c3b01d20445105f1669446bd3.
6
+
7
+ ## 2.1.0-beta.19
8
+
9
+ - Release candidate for main commit 71d76385f722b1202880f26b14ea3916a07852eb.
10
+
11
+ ## 2.1.0-beta.18
12
+
13
+ - Release candidate for main commit 46f93ecf8c2c8aab1afd0f23f5f62a87cbb45f6d.
14
+
15
+ ## 2.1.0-beta.17
16
+
17
+ - Release candidate for main commit c4466ffc8a30cb99740b50687a62b92cec9ba10e.
18
+
19
+ ## 2.1.0-beta.16
20
+
21
+ - Release candidate for main commit c0d8a1c4be519e1c1abc59e0304efc64f371634d.
22
+
23
+ ## Unreleased
24
+
25
+ - Add `arrayBuffer()` and `bytes()` to `HttpResponse` so `ctx.http` consumers can read binary-safe upstream bodies; internal response handling is now byte-first.
26
+
27
+ ## 2.1.0-beta.15
28
+
29
+ - Release candidate for main commit 4f51232d87828082f117dcc9f0c257a46f37c040.
30
+
31
+ ## 2.1.0-beta.14
32
+
33
+ - Release candidate for main commit 6eb132be6abf34ad9a70bfe28c6d26b36348ff4a.
34
+
35
+ ## 2.1.0-beta.13
36
+
37
+ - Release candidate for main commit d7b12716f54781df3e40206144c167844a485f8f.
38
+
39
+ ## 2.1.0-beta.12
40
+
41
+ - Release candidate for main commit 2bc1061c6a68facaa2efde08bee31bf5cd96945e.
42
+
43
+ ## 2.1.0-beta.11
44
+
45
+ - Release candidate for main commit b98ddc5024698f8c79e05e3295f7c8c8e8fe5a8a.
46
+
47
+ ## 2.1.0-beta.9
48
+
49
+ - Preserve raw stealth response bytes through the public SDK response wrapper.
50
+ - Add `arrayBuffer()` and `bytes()` to `StealthResponse` so consumers can inspect binary-safe upstream bodies.
51
+
52
+ ## 2.1.0-beta.8
53
+
54
+ - Fix release automation for compiled `dist` package exports by building before package self-tests.
55
+ - Provide `GH_TOKEN` to the GitHub release creation step.
56
+
57
+ ## 2.1.0-beta.7
58
+
59
+ - Publish compiled `dist` exports for npm consumers so Next.js/Vercel builds do not parse TypeScript from `node_modules`.
60
+ - Keep public CLI/template source files in the package while routing library exports through generated JavaScript and declarations.
61
+
62
+ ## 2.1.0-beta.6
63
+
64
+ - Public repository clean-import release for `APIFuseHQ/provider-sdk`.
65
+ - Preserves the monorepo SDK exports required by ApiFuse provider registry cutover, including `./contract` and provider i18n helpers.
66
+
67
+
68
+ ## 2.1.0-beta.5
69
+
70
+ - Republish the bounty workspace DX hardening that accepts generated readonly metadata and factored `defineOperation()` maps during standalone TypeScript checks.
71
+ - Ensure new bounty workspaces can install the public SDK version that matches their generated scaffold and pass `bun run check` immediately after bootstrap.
72
+
73
+ ## 2.1.0-beta.4
74
+
75
+ - Align `apifuse create` with the bounty program topology: external contributors use the standalone one-provider-repository scaffold even when their assigned repo contains workspace-like files.
76
+ - Stop auto-detecting `providers/` directories as public monorepo scaffolds. `--preset monorepo` is now reserved for the private APIFuse monorepo where `packages/provider-sdk` is actually present.
77
+ - Remove public CLI/docs examples that present monorepo placement as a contributor workflow.
78
+
79
+ ## 2.1.0-beta.3
80
+
81
+ - Replace the legacy TypeScript request transport with `ctx.stealth`, backed by `impit` browser-grade TLS/HTTP2 impersonation without Python runtime dependencies.
82
+ - Add the public `apifuse submit-check` / `apifuse bounty-check` CLI for score-based pre-submission provider quality checks.
83
+ - Ship `SUBMISSION.md` in the npm package so bounty contributors can follow the checklist without access to the private monorepo.
84
+ - Include submit-check in generated provider validation scripts and packed-artifact smoke coverage.
85
+ - Warn, instead of hard-block, generated OAuth starters that have not yet declared persisted credential keys.
86
+
3
87
  ## 2.1.0-beta.2
4
88
 
5
89
  - Harden public bounty contributor DX with server-contract accurate README and generated Provider smoke examples.
6
90
  - Add packed-artifact regression checks so stale `connection: null` or missing `requestId` examples cannot ship again.
7
91
  - Extend clean-room packed SDK smoke coverage to boot the generated dev server and call `/health` plus `POST /v1/ping`.
8
- - Document credential, auth-flow, TLS, browser, and Bun trusted-dependency troubleshooting for SDK-only local development.
92
+ - Document credential, auth-flow, stealth, browser, and Bun trusted-dependency troubleshooting for SDK-only local development.
9
93
 
10
94
  ## 2.1.0-beta.1
11
95
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @apifuse/provider-sdk
2
2
 
3
- ApiFuse Provider SDK — build provider declarations and runtimes with one public SDK surface and one canonical CLI.
3
+ APIFuse Provider SDK — build provider declarations and runtimes with one public SDK surface and one canonical CLI.
4
4
 
5
5
  ## Install
6
6
 
@@ -29,13 +29,11 @@ The canonical `create` flow:
29
29
  3. runs baseline validation,
30
30
  4. prints the exact next local-dev command.
31
31
 
32
- ### Monorepo preset
32
+ ### Repository shape
33
33
 
34
- Inside the ApiFuse repository:
34
+ The Provider SDK is a public bounty-contributor tool first. External bounty workspaces are one-provider repositories initialized from the standalone create flow. The generated provider must be installable without private APIFuse monorepo access.
35
35
 
36
- ```bash
37
- apifuse create my-provider --preset monorepo
38
- ```
36
+ Do not use internal monorepo placement for bounty workspaces. Accepted provider work is imported into the private APIFuse monorepo later by maintainers or trusted automation.
39
37
 
40
38
  ## Provider server contract
41
39
 
@@ -58,6 +56,7 @@ Removed legacy runtime paths are not supported:
58
56
  cd my-provider
59
57
  bun run check
60
58
  bun run test
59
+ bun run submit-check -- --smoke
61
60
  bun run dev
62
61
  ```
63
62
 
@@ -70,7 +69,7 @@ curl -s -X POST http://localhost:3900/v1/ping \
70
69
  -d '{"requestId":"req_local_ping","input":{"value":"hello"},"headers":{}}'
71
70
  ```
72
71
 
73
- The operation request body is the same envelope used by the ApiFuse gateway:
72
+ The operation request body is the same envelope used by the APIFuse gateway:
74
73
 
75
74
  | Field | Required | Notes |
76
75
  |---|---:|---|
@@ -88,7 +87,7 @@ curl -s -X POST http://localhost:3900/v1/me \
88
87
  "requestId":"req_local_me",
89
88
  "input":{},
90
89
  "connection":{
91
- "id":"conn_local_debug",
90
+ "id":"af_con_local_debug",
92
91
  "mode":"credentials",
93
92
  "secrets":{"apiKey":"dev-only-secret"},
94
93
  "scopes":[],
@@ -121,17 +120,32 @@ the bad request path; provider/runtime failures include `code`, `message`, and
121
120
  `ctx.credential.get("key")` or `ctx.credential.getAccessToken()`.
122
121
  - **Provider env secrets**: declare `secrets[]`, set values in your shell or
123
122
  `.env`, and read only those names through `ctx.env.get("NAME")`.
123
+ - **Credentials auth flows**: prefer `defineCredentialsAuth()` over hand-written
124
+ `auth.flow`. Declare the form fields and credential keys once, then put the
125
+ upstream login/session creation in `login(ctx, input)`. Return
126
+ `credentialsAuthChallenge("otp" | "manualApproval" | ...)` for MFA, CAPTCHA
127
+ handoff, or user-approved login branches. The helper returns
128
+ `{ auth, credential, context }` for `defineProvider()` and always completes
129
+ with `data.credential`, which is the value Gateway persists onto the
130
+ connection.
124
131
  - **Auth flows**: call `/auth/start`, then `/auth/continue` with the same
125
132
  `flowId`; preserve any returned `contextPatch` in the next local request's
126
133
  `context` object.
127
- - **TLS-sensitive providers**: if Bun reports blocked lifecycle scripts after
128
- install, run `bun pm untrusted`; when it lists trusted SDK native dependencies
129
- such as `koffi`, run `bun pm trust koffi` (or `bun pm trust`) before debugging
130
- `ctx.tls` failures.
134
+ - **Stealth-sensitive providers**: use `ctx.http` for normal JSON/REST calls and
135
+ `ctx.stealth.fetch()` when you need browser-like session or cookie control.
136
+ `ctx.stealth.fetch()` uses the impit-backed browser stealth transport and
137
+ accepts request controls for `params`, `proxy`, `timeout`, `profile`,
138
+ `redirect`, `throwOnHttpError`, and `stealth.insecureSkipVerify`. For login
139
+ flows that must inspect intermediate `Location`/`Set-Cookie` headers, create
140
+ a session with `ctx.stealth.createSession()` and use `session.redirects.run()`;
141
+ inspect accumulated cookies through `session.cookies`. Select an SDK stealth
142
+ `profile` such as `chrome-146`; do not tune JA3, HTTP/2 SETTINGS, or
143
+ pseudo-header order in provider code. Chrome/Firefox-style profiles are
144
+ supported; use `ctx.browser` when Safari-specific behavior is required.
131
145
  - **Browser providers**: for TypeScript Providers use `runtime: "browser"` plus
132
146
  `browser.engine: "playwright-stealth"`; `nodriver` is a Python-runtime path.
133
147
  Install local browser assets with `bunx playwright install chromium` when
134
- using the Playwright runtime, or set `CDP_POOL_URL`/`APIFUSE_CDP_POOL_URL`
148
+ using the Playwright runtime, or set `APIFUSE__CDP_POOL__URL`
135
149
  when debugging against a remote browser pool.
136
150
 
137
151
  ## Authoring ergonomics
@@ -147,6 +161,9 @@ const search = defineOperation({
147
161
  async handler(ctx, input) {
148
162
  return { count: input.q.length }
149
163
  },
164
+ healthCheckUnsupported: {
165
+ reason: "Example operation only; replace with a real upstream probe.",
166
+ },
150
167
  })
151
168
 
152
169
  export default defineProvider({
@@ -172,6 +189,24 @@ Every operation must declare exactly one of:
172
189
  The generated `ping` operation uses `healthCheckUnsupported` only because it is
173
190
  a local scaffold check, not a real upstream API probe.
174
191
 
192
+ `healthCheck.cases[].assertions` receives `{ data, status, durationMs, meta }`.
193
+ `data` is the operation output parsed by the declared output schema:
194
+
195
+ ```ts
196
+ healthCheck: {
197
+ interval: "5m",
198
+ cases: [{
199
+ name: "search responds",
200
+ input: { q: "weather" },
201
+ assertions: ({ data, status, durationMs }) => {
202
+ if (status !== 200 || data.count < 1 || durationMs > 3000) {
203
+ return { status: "degraded", label: "unexpected search baseline" }
204
+ }
205
+ },
206
+ }],
207
+ }
208
+ ```
209
+
175
210
  ### Operation annotations
176
211
 
177
212
  Operations declare non-functional metadata via `annotations`:
@@ -193,16 +228,28 @@ Operations declare non-functional metadata via `annotations`:
193
228
  apifuse create <name>
194
229
  apifuse dev [path]
195
230
  apifuse check [path]
196
- apifuse record [path] --operation <operation> --params '{"value":"hello"}'
231
+ apifuse record providers/korea-air-quality --operation realtime --params '{"stationName":"종로구"}'
197
232
  apifuse test [path]
198
- apifuse perf <path> --operation <operation>
233
+ apifuse submit-check [path] --tier bronze --markdown submission-report.md
234
+ apifuse bounty-check [path]
235
+ apifuse perf providers/korea-air-quality --operation realtime --params '{"stationName":"종로구"}'
199
236
  ```
200
237
 
201
238
  `apifuse record` is for real upstream-backed operations that declare
202
- `upstream.baseUrl` and call the upstream through `ctx.http` or `ctx.tls`. The
239
+ `upstream.baseUrl` and call the upstream through `ctx.http` or `ctx.stealth`. The
203
240
  generated local-only `ping` operation intentionally has no upstream and should
204
241
  be replaced before recording fixtures.
205
242
 
243
+ ## Bounty submission readiness
244
+
245
+ Standalone providers include a pre-submission script:
246
+
247
+ ```bash
248
+ bun run submit-check -- --smoke
249
+ ```
250
+
251
+ 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.
252
+
206
253
  ## Scope boundary
207
254
 
208
255
  Generator v1 scaffolds **TypeScript providers only** for this redesign. Python generation remains future work.
@@ -213,6 +260,6 @@ Generator v1 scaffolds **TypeScript providers only** for this redesign. Python g
213
260
  Provider cataloging, deployment enrollment, docs indexing, and runtime discovery are internal platform-registry responsibilities and are not part of the public `@apifuse/provider-sdk` contract.
214
261
 
215
262
  External bounty contributors should submit standalone Provider source plus
216
- `bun run check` / `bun run test` evidence. ApiFuse maintainers own monorepo
263
+ `bun run check` / `bun run test` evidence. APIFuse maintainers own monorepo
217
264
  import, registry generation, deployment projection checks, and release
218
265
  publishing.