@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.31

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 (342) hide show
  1. package/AUTHORING.md +493 -5
  2. package/CHANGELOG.md +131 -1
  3. package/README.md +52 -6
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +106 -62
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +63 -55
  8. package/bin/apifuse-pack-check.ts +22 -2
  9. package/bin/apifuse-pack-smoke.ts +78 -82
  10. package/bin/apifuse-pack-types.ts +583 -0
  11. package/bin/apifuse-perf.ts +59 -140
  12. package/bin/apifuse-record.ts +698 -113
  13. package/bin/apifuse-submit-check.ts +517 -44
  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.ts +1 -1
  18. package/dist/auth-turn/index.d.ts +4 -4
  19. package/dist/auth-turn/index.js +1 -1
  20. package/dist/auth.d.ts +16 -2
  21. package/dist/auth.js +76 -18
  22. package/dist/ceremonies/index.d.ts +9 -1
  23. package/dist/ceremonies/index.js +117 -29
  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/cli/templates/provider/README.md.tpl +4 -4
  32. package/dist/config/loader.d.ts +176 -17
  33. package/dist/config/loader.js +434 -161
  34. package/dist/contract-serialization.d.ts +2 -2
  35. package/dist/contract-serialization.js +7 -14
  36. package/dist/contract-types.d.ts +3 -2
  37. package/dist/contract.d.ts +3 -3
  38. package/dist/contract.js +6 -6
  39. package/dist/declaration-validation.d.ts +23 -0
  40. package/dist/declaration-validation.js +159 -0
  41. package/dist/define.d.ts +13 -1
  42. package/dist/define.js +391 -122
  43. package/dist/dev.d.ts +1 -1
  44. package/dist/dev.js +1 -1
  45. package/dist/error-resolution.d.ts +4 -0
  46. package/dist/error-resolution.js +123 -0
  47. package/dist/errors.d.ts +19 -1
  48. package/dist/errors.js +41 -3
  49. package/dist/fixture-sanitization.d.ts +26 -0
  50. package/dist/fixture-sanitization.js +216 -0
  51. package/dist/i18n/catalog.d.ts +2 -2
  52. package/dist/i18n/catalog.js +4 -10
  53. package/dist/i18n/index.d.ts +2 -2
  54. package/dist/i18n/index.js +2 -2
  55. package/dist/i18n/keys.d.ts +2 -2
  56. package/dist/index.d.ts +50 -42
  57. package/dist/index.js +41 -37
  58. package/dist/lint.d.ts +6 -1
  59. package/dist/lint.js +370 -18
  60. package/dist/native-address.d.ts +43 -0
  61. package/dist/native-address.js +281 -0
  62. package/dist/native-egress-policy.d.ts +31 -0
  63. package/dist/native-egress-policy.js +288 -0
  64. package/dist/observability.d.ts +5 -2
  65. package/dist/observability.js +48 -1
  66. package/dist/provider.d.ts +13 -11
  67. package/dist/provider.js +10 -9
  68. package/dist/public-schema-field-lint.d.ts +1 -1
  69. package/dist/recipes/gov-api.js +1 -1
  70. package/dist/runtime/auth-flow.d.ts +3 -1
  71. package/dist/runtime/auth-flow.js +8 -3
  72. package/dist/runtime/browser.d.ts +1 -1
  73. package/dist/runtime/browser.js +138 -40
  74. package/dist/runtime/cache.d.ts +2 -1
  75. package/dist/runtime/cache.js +173 -23
  76. package/dist/runtime/choice-wordlist.d.ts +9 -0
  77. package/dist/runtime/choice-wordlist.js +138 -0
  78. package/dist/runtime/choice.d.ts +14 -1
  79. package/dist/runtime/choice.js +566 -101
  80. package/dist/runtime/credential.d.ts +1 -1
  81. package/dist/runtime/credential.js +1 -1
  82. package/dist/runtime/env.d.ts +1 -1
  83. package/dist/runtime/executor.d.ts +1 -1
  84. package/dist/runtime/executor.js +25 -3
  85. package/dist/runtime/http.d.ts +3 -2
  86. package/dist/runtime/http.js +517 -55
  87. package/dist/runtime/insights.d.ts +1 -1
  88. package/dist/runtime/insights.js +6 -13
  89. package/dist/runtime/instrumentation.d.ts +2 -2
  90. package/dist/runtime/instrumentation.js +371 -23
  91. package/dist/runtime/keyring.js +1 -1
  92. package/dist/runtime/namespace.js +1 -1
  93. package/dist/runtime/native-network-errors.d.ts +33 -0
  94. package/dist/runtime/native-network-errors.js +69 -0
  95. package/dist/runtime/native-network.d.ts +96 -0
  96. package/dist/runtime/native-network.js +1232 -0
  97. package/dist/runtime/ocr.d.ts +29 -0
  98. package/dist/runtime/ocr.js +440 -0
  99. package/dist/runtime/otlp.d.ts +1 -1
  100. package/dist/runtime/perf.d.ts +1 -1
  101. package/dist/runtime/provider.d.ts +1 -1
  102. package/dist/runtime/provider.js +1 -2
  103. package/dist/runtime/proxy-errors.d.ts +1 -1
  104. package/dist/runtime/proxy-errors.js +9 -7
  105. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  106. package/dist/runtime/proxy-nodemaven.js +146 -0
  107. package/dist/runtime/proxy-retry-policy.d.ts +2 -2
  108. package/dist/runtime/proxy-retry-policy.js +2 -2
  109. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  110. package/dist/runtime/proxy-telemetry.js +58 -52
  111. package/dist/runtime/redirects.d.ts +29 -0
  112. package/dist/runtime/redirects.js +36 -0
  113. package/dist/runtime/redis.d.ts +1 -1
  114. package/dist/runtime/redis.js +5 -5
  115. package/dist/runtime/request-options.d.ts +68 -1
  116. package/dist/runtime/request-options.js +548 -0
  117. package/dist/runtime/resolver-config.d.ts +6 -0
  118. package/dist/runtime/resolver-config.js +6 -0
  119. package/dist/runtime/resolver-public.d.ts +1 -0
  120. package/dist/runtime/resolver-public.js +1 -0
  121. package/dist/runtime/resolver-shared.d.ts +3 -0
  122. package/dist/runtime/resolver-shared.js +12 -0
  123. package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
  124. package/dist/runtime/resolver-vendors/bindings.js +40 -0
  125. package/dist/runtime/resolver-vendors/browser.d.ts +20 -0
  126. package/dist/runtime/resolver-vendors/browser.js +282 -0
  127. package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
  128. package/dist/runtime/resolver-vendors/hosts.js +33 -0
  129. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  130. package/dist/runtime/resolver-vendors/twocaptcha.js +368 -0
  131. package/dist/runtime/resolver-vendors/types.d.ts +83 -0
  132. package/dist/runtime/resolver-vendors/types.js +69 -0
  133. package/dist/runtime/resolver.d.ts +59 -0
  134. package/dist/runtime/resolver.js +705 -0
  135. package/dist/runtime/secrets.d.ts +27 -0
  136. package/dist/runtime/secrets.js +51 -0
  137. package/dist/runtime/state.d.ts +5 -2
  138. package/dist/runtime/state.js +280 -74
  139. package/dist/runtime/stealth-cookies.d.ts +20 -0
  140. package/dist/runtime/stealth-cookies.js +111 -0
  141. package/dist/runtime/stealth.d.ts +30 -5
  142. package/dist/runtime/stealth.js +523 -259
  143. package/dist/runtime/stt.d.ts +1 -1
  144. package/dist/runtime/stt.js +12 -27
  145. package/dist/runtime/timeout.d.ts +5 -0
  146. package/dist/runtime/timeout.js +12 -0
  147. package/dist/runtime/trace.d.ts +2 -2
  148. package/dist/runtime/trace.js +2 -4
  149. package/dist/runtime/waterfall.d.ts +1 -1
  150. package/dist/schema.d.ts +1 -1
  151. package/dist/schema.js +7 -15
  152. package/dist/serve.d.ts +1 -1
  153. package/dist/serve.js +1 -1
  154. package/dist/server/index.d.ts +7 -7
  155. package/dist/server/index.js +6 -6
  156. package/dist/server/self-test-input-tokens.d.ts +2 -1
  157. package/dist/server/self-test-input-tokens.js +18 -14
  158. package/dist/server/self-test-redaction.d.ts +1 -1
  159. package/dist/server/self-test-redaction.js +1 -1
  160. package/dist/server/self-test.d.ts +117 -3
  161. package/dist/server/self-test.js +787 -151
  162. package/dist/server/serve-implementation.d.ts +210 -0
  163. package/dist/server/serve-implementation.js +2078 -0
  164. package/dist/server/serve.d.ts +1 -70
  165. package/dist/server/serve.js +1 -1143
  166. package/dist/server/types.d.ts +34 -9
  167. package/dist/server/types.js +8 -1
  168. package/dist/stateful/errors.d.ts +19 -0
  169. package/dist/stateful/errors.js +24 -0
  170. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  171. package/dist/stateful/http-provider-event-emitter.js +237 -0
  172. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  173. package/dist/stateful/http-session-owner-registry.js +210 -0
  174. package/dist/stateful/index.d.ts +18 -0
  175. package/dist/stateful/index.js +18 -0
  176. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  177. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  178. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  179. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  180. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  181. package/dist/stateful/provider-event-pipeline.js +1 -0
  182. package/dist/stateful/provider-events.d.ts +101 -0
  183. package/dist/stateful/provider-events.js +289 -0
  184. package/dist/stateful/session-key.d.ts +15 -0
  185. package/dist/stateful/session-key.js +86 -0
  186. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  187. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  188. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  189. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  190. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  191. package/dist/stateful/stateful-provider-adapter.js +287 -0
  192. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  193. package/dist/stateful/stateful-provider-observability.js +161 -0
  194. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  195. package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
  196. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  197. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  198. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  199. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  200. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  201. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  202. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  203. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  204. package/dist/stateful-signing.d.ts +18 -0
  205. package/dist/stateful-signing.js +27 -0
  206. package/dist/stealth/profiles.d.ts +1 -1
  207. package/dist/stealth/profiles.js +21 -21
  208. package/dist/stream-evidence.d.ts +74 -0
  209. package/dist/stream-evidence.js +785 -0
  210. package/dist/stream.d.ts +1 -1
  211. package/dist/stream.js +7 -1
  212. package/dist/testing/index.d.ts +3 -2
  213. package/dist/testing/index.js +3 -2
  214. package/dist/testing/run.d.ts +32 -2
  215. package/dist/testing/run.js +488 -28
  216. package/dist/types.d.ts +566 -19
  217. package/dist/types.js +1 -0
  218. package/dist/user-input.d.ts +30 -0
  219. package/dist/user-input.js +66 -0
  220. package/package.json +42 -7
  221. package/src/auth-turn/index.ts +2 -2
  222. package/src/auth.ts +146 -86
  223. package/src/ceremonies/index.ts +167 -92
  224. package/src/cli/commands.ts +10 -0
  225. package/src/cli/create.ts +42 -35
  226. package/src/cli/prompt-assets.ts +865 -0
  227. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  228. package/src/cli/templates/provider/README.md.tpl +4 -4
  229. package/src/config/loader.ts +667 -289
  230. package/src/contract-serialization.ts +10 -18
  231. package/src/contract-types.ts +3 -2
  232. package/src/contract.ts +14 -28
  233. package/src/declaration-validation.ts +202 -0
  234. package/src/define.ts +631 -495
  235. package/src/dev.ts +4 -9
  236. package/src/error-resolution.ts +128 -0
  237. package/src/errors.ts +56 -11
  238. package/src/fixture-sanitization.ts +247 -0
  239. package/src/i18n/catalog.ts +10 -32
  240. package/src/i18n/index.ts +2 -2
  241. package/src/i18n/keys.ts +5 -11
  242. package/src/index.ts +158 -44
  243. package/src/lint.ts +488 -154
  244. package/src/native-address.ts +340 -0
  245. package/src/native-egress-policy.ts +358 -0
  246. package/src/observability.ts +51 -1
  247. package/src/provider.ts +66 -11
  248. package/src/public-schema-field-lint.ts +7 -33
  249. package/src/recipes/gov-api.ts +2 -5
  250. package/src/runtime/auth-flow.ts +13 -7
  251. package/src/runtime/browser.ts +252 -207
  252. package/src/runtime/cache.ts +209 -81
  253. package/src/runtime/choice-wordlist.ts +145 -0
  254. package/src/runtime/choice.ts +758 -197
  255. package/src/runtime/credential.ts +2 -2
  256. package/src/runtime/env.ts +1 -1
  257. package/src/runtime/executor.ts +37 -19
  258. package/src/runtime/http.ts +645 -65
  259. package/src/runtime/insights.ts +15 -53
  260. package/src/runtime/instrumentation.ts +530 -67
  261. package/src/runtime/keyring.ts +7 -19
  262. package/src/runtime/namespace.ts +2 -7
  263. package/src/runtime/native-network-errors.ts +99 -0
  264. package/src/runtime/native-network.ts +1605 -0
  265. package/src/runtime/ocr.ts +523 -0
  266. package/src/runtime/otlp.ts +12 -23
  267. package/src/runtime/perf.ts +1 -1
  268. package/src/runtime/provider.ts +4 -9
  269. package/src/runtime/proxy-errors.ts +29 -42
  270. package/src/runtime/proxy-nodemaven.ts +221 -0
  271. package/src/runtime/proxy-retry-policy.ts +3 -3
  272. package/src/runtime/proxy-telemetry.ts +84 -77
  273. package/src/runtime/redirects.ts +66 -0
  274. package/src/runtime/redis.ts +10 -13
  275. package/src/runtime/request-options.ts +679 -9
  276. package/src/runtime/resolver-config.ts +6 -0
  277. package/src/runtime/resolver-public.ts +18 -0
  278. package/src/runtime/resolver-shared.ts +17 -0
  279. package/src/runtime/resolver-vendors/bindings.ts +56 -0
  280. package/src/runtime/resolver-vendors/browser.ts +408 -0
  281. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  282. package/src/runtime/resolver-vendors/twocaptcha.ts +500 -0
  283. package/src/runtime/resolver-vendors/types.ts +173 -0
  284. package/src/runtime/resolver.ts +1060 -0
  285. package/src/runtime/secrets.ts +64 -0
  286. package/src/runtime/state.ts +399 -161
  287. package/src/runtime/stealth-cookies.ts +132 -0
  288. package/src/runtime/stealth.ts +681 -295
  289. package/src/runtime/stt.ts +39 -113
  290. package/src/runtime/timeout.ts +18 -0
  291. package/src/runtime/trace.ts +14 -44
  292. package/src/runtime/waterfall.ts +5 -18
  293. package/src/schema.ts +23 -84
  294. package/src/serve.ts +6 -1
  295. package/src/server/index.ts +30 -7
  296. package/src/server/self-test-input-tokens.ts +29 -14
  297. package/src/server/self-test-redaction.ts +2 -2
  298. package/src/server/self-test.ts +1030 -180
  299. package/src/server/serve-implementation.ts +3062 -0
  300. package/src/server/serve.ts +1 -1781
  301. package/src/server/types.ts +12 -13
  302. package/src/stateful/README.md +146 -0
  303. package/src/stateful/errors.ts +35 -0
  304. package/src/stateful/http-provider-event-emitter.ts +314 -0
  305. package/src/stateful/http-session-owner-registry.ts +306 -0
  306. package/src/stateful/index.ts +18 -0
  307. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  308. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  309. package/src/stateful/provider-event-pipeline.ts +61 -0
  310. package/src/stateful/provider-events.ts +462 -0
  311. package/src/stateful/session-key.ts +111 -0
  312. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  313. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  314. package/src/stateful/stateful-provider-adapter.ts +562 -0
  315. package/src/stateful/stateful-provider-observability.ts +261 -0
  316. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  317. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  318. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  319. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  320. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  321. package/src/stateful-signing.ts +46 -0
  322. package/src/stealth/profiles.ts +27 -33
  323. package/src/stream-evidence.ts +988 -0
  324. package/src/stream.ts +16 -20
  325. package/src/testing/index.ts +11 -2
  326. package/src/testing/run.ts +668 -74
  327. package/src/types.ts +665 -35
  328. package/src/user-input.ts +118 -0
  329. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  330. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  331. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  332. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  333. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  334. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  335. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  336. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  337. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  338. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  339. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  340. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  341. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  342. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -1,36 +1,39 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
2
 
3
- import Ajv2020 from "ajv/dist/2020.js";
4
-
5
- import { AUTH_TURN_SCHEMA, type KnownAuthTurnKind } from "../auth-turn";
3
+ import { AUTH_TURN_SCHEMA, type KnownAuthTurnKind } from "../auth-turn/index.js";
6
4
  import {
7
5
  FlowExpiredError,
8
6
  ProviderSecretError,
9
7
  TurnValidationError,
10
8
  ValidationError,
11
- } from "../errors";
12
- import type {
13
- AuthFlowDefinition,
14
- AuthFlowInputHandler,
15
- AuthTurn,
16
- FlowContext,
17
- } from "../types";
9
+ } from "../errors.js";
10
+ import type { AuthFlowDefinition, AuthFlowInputHandler, AuthTurn, FlowContext } from "../types.js";
18
11
 
19
12
  type TurnKind = KnownAuthTurnKind;
20
13
 
21
14
  type CeremonyHandler = AuthFlowInputHandler;
22
15
 
23
16
  type JsonObject = Record<string, unknown>;
24
-
25
- const ajv = new Ajv2020({ allErrors: true, strict: true, strictSchema: true });
26
-
27
- // Runtime ceremony-output validation derives from the exported versioned
28
- // contract: it compiles the exact AUTH_TURN_SCHEMA document shipped at
29
- // dist/auth-turn/auth-turn.v1.schema.json, so the two cannot drift.
30
- const validateAuthTurn = ajv.compile(AUTH_TURN_SCHEMA);
17
+ type JsonSchemaNode = {
18
+ readonly additionalProperties?: boolean;
19
+ readonly minimum?: number;
20
+ readonly minLength?: number;
21
+ readonly properties?: Readonly<Record<string, JsonSchemaNode>>;
22
+ readonly required?: readonly string[];
23
+ readonly type?: "number" | "object" | "string";
24
+ };
31
25
 
32
26
  const OAUTH2_STATE_KEY = "__oauth2_state";
33
27
  const OAUTH2_PKCE_VERIFIER_KEY = "__oauth2_pkce_verifier";
28
+ export const OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY = "APIFUSE__AUTH_PROXY__URL";
29
+ export const OAUTH2_PROXIED_PKCE_VERIFIER_KEY = "__oauth2_proxied_pkce_verifier";
30
+ const OAUTH2_PROXIED_RESERVED_AUTHORIZE_PARAMS = new Set([
31
+ "client_id",
32
+ "response_type",
33
+ "state",
34
+ "code_challenge",
35
+ "code_challenge_method",
36
+ ]);
34
37
  const DEVICE_FLOW_KEY = "__device_flow";
35
38
  const MAGIC_LINK_KEY = "__magic_link";
36
39
  const COMBINED_STAGE_KEY = "__combined_stage";
@@ -41,14 +44,61 @@ function isRecord(value: unknown): value is JsonObject {
41
44
  return !!value && typeof value === "object" && !Array.isArray(value);
42
45
  }
43
46
 
47
+ function validateSchemaNode(
48
+ value: unknown,
49
+ schema: JsonSchemaNode,
50
+ path: string,
51
+ errors: string[],
52
+ ): void {
53
+ if (schema.type === "object") {
54
+ if (!isRecord(value)) {
55
+ errors.push(`${path} must be object`);
56
+ return;
57
+ }
58
+ const properties = schema.properties ?? {};
59
+ for (const required of schema.required ?? []) {
60
+ if (!Object.hasOwn(value, required) || value[required] === undefined) {
61
+ errors.push(`${path} must have required property '${required}'`);
62
+ }
63
+ }
64
+ if (schema.additionalProperties === false) {
65
+ for (const key of Object.keys(value)) {
66
+ if (!Object.hasOwn(properties, key)) {
67
+ errors.push(`${path} must NOT have additional property '${key}'`);
68
+ }
69
+ }
70
+ }
71
+ for (const [key, childSchema] of Object.entries(properties)) {
72
+ if (Object.hasOwn(value, key) && value[key] !== undefined) {
73
+ validateSchemaNode(value[key], childSchema, `${path}/${key}`, errors);
74
+ }
75
+ }
76
+ return;
77
+ }
78
+
79
+ if (schema.type === "string") {
80
+ if (typeof value !== "string") {
81
+ errors.push(`${path} must be string`);
82
+ } else if (schema.minLength !== undefined && value.length < schema.minLength) {
83
+ errors.push(`${path} must NOT have fewer than ${schema.minLength} characters`);
84
+ }
85
+ return;
86
+ }
87
+
88
+ if (schema.type === "number") {
89
+ if (typeof value !== "number" || !Number.isFinite(value)) {
90
+ errors.push(`${path} must be number`);
91
+ } else if (schema.minimum !== undefined && value < schema.minimum) {
92
+ errors.push(`${path} must be >= ${schema.minimum}`);
93
+ }
94
+ }
95
+ }
96
+
44
97
  function ensureRecord(value: unknown): JsonObject {
45
98
  return isRecord(value) ? value : {};
46
99
  }
47
100
 
48
- function createTurn(
49
- kind: TurnKind,
50
- options: Omit<AuthTurn, "kind" | "turnId"> = {},
51
- ): AuthTurn {
101
+ function createTurn(kind: TurnKind, options: Omit<AuthTurn, "kind" | "turnId"> = {}): AuthTurn {
52
102
  return {
53
103
  kind,
54
104
  turnId: randomUUID(),
@@ -69,11 +119,7 @@ function getRequiredEnv(ctx: FlowContext, key: string): string {
69
119
  }
70
120
 
71
121
  function toBase64Url(input: Buffer): string {
72
- return input
73
- .toString("base64")
74
- .replace(/\+/g, "-")
75
- .replace(/\//g, "_")
76
- .replace(/=+$/g, "");
122
+ return input.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
77
123
  }
78
124
 
79
125
  function createCodeVerifier(): string {
@@ -130,10 +176,7 @@ async function runCeremonyHandler(
130
176
  }
131
177
  }
132
178
 
133
- function getString(
134
- input: Record<string, unknown>,
135
- key: string,
136
- ): string | undefined {
179
+ function getString(input: Record<string, unknown>, key: string): string | undefined {
137
180
  const value = input[key];
138
181
  return typeof value === "string" ? value : undefined;
139
182
  }
@@ -142,10 +185,7 @@ function getNestedRecord(ctx: FlowContext, key: string): JsonObject {
142
185
  return ensureRecord(ctx.context.get(key));
143
186
  }
144
187
 
145
- function buildJsonSchemaForm(
146
- expectedInput: JsonObject,
147
- hint: string,
148
- ): AuthTurn {
188
+ function buildJsonSchemaForm(expectedInput: JsonObject, hint: string): AuthTurn {
149
189
  return createTurn("form", {
150
190
  hint,
151
191
  expectedInput: withDeclaredFormFieldOrder(expectedInput),
@@ -160,10 +200,7 @@ function withDeclaredFormFieldOrder(expectedInput: JsonObject): JsonObject {
160
200
  }
161
201
 
162
202
  const existingOrder = expectedInput[FORM_FIELD_ORDER_EXTENSION];
163
- if (
164
- Array.isArray(existingOrder) &&
165
- existingOrder.every((value) => typeof value === "string")
166
- ) {
203
+ if (Array.isArray(existingOrder) && existingOrder.every((value) => typeof value === "string")) {
167
204
  return expectedInput;
168
205
  }
169
206
 
@@ -174,16 +211,17 @@ function withDeclaredFormFieldOrder(expectedInput: JsonObject): JsonObject {
174
211
  }
175
212
 
176
213
  export function validateCeremonyOutput(turn: unknown): AuthTurn {
177
- if (!validateAuthTurn(turn)) {
178
- const detail = validateAuthTurn.errors
179
- ?.map(
180
- (error) => `${error.instancePath || "$"} ${error.message ?? "invalid"}`,
181
- )
182
- .join("; ");
183
- throw new TurnValidationError(detail || "Invalid AuthTurn output");
214
+ // Evaluate the exact exported versioned schema without eagerly initializing
215
+ // a general-purpose JSON Schema compiler in every provider process. Contract
216
+ // parity tests compare this focused evaluator with AJV over all fixtures and
217
+ // edge probes, so the runtime and shipped document remain locked together.
218
+ const errors: string[] = [];
219
+ validateSchemaNode(turn, AUTH_TURN_SCHEMA, "", errors);
220
+ if (errors.length > 0) {
221
+ throw new TurnValidationError(errors.join("; "));
184
222
  }
185
223
 
186
- return turn;
224
+ return turn as AuthTurn;
187
225
  }
188
226
 
189
227
  export function createOAuth2Ceremony(options: {
@@ -214,10 +252,7 @@ export function createOAuth2Ceremony(options: {
214
252
  if (options.usePKCE) {
215
253
  const verifier = createCodeVerifier();
216
254
  ctx.context.set(OAUTH2_PKCE_VERIFIER_KEY, verifier);
217
- authorizeUrl.searchParams.set(
218
- "code_challenge",
219
- createCodeChallenge(verifier),
220
- );
255
+ authorizeUrl.searchParams.set("code_challenge", createCodeChallenge(verifier));
221
256
  authorizeUrl.searchParams.set("code_challenge_method", "S256");
222
257
  }
223
258
 
@@ -270,13 +305,78 @@ export function createOAuth2Ceremony(options: {
270
305
  ctx,
271
306
  input,
272
307
  ),
273
- abort: async () =>
274
- validateCeremonyOutput(
275
- createTurn("abort", { hint: "OAuth flow aborted." }),
276
- ),
308
+ abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "OAuth flow aborted." })),
277
309
  };
278
310
  }
279
311
 
312
+ /**
313
+ * Builds the start handler for a custom-scheme OAuth provider. The shared
314
+ * auth-proxy owns state minting and callback capture, while the provider owns
315
+ * token exchange and any following authentication turns.
316
+ */
317
+ export function createOAuth2ProxiedStart(
318
+ options: import("../types.js").ProxiedOAuthConfig,
319
+ ): import("../types.js").AuthFlowStartHandler {
320
+ const pkce = options.pkce ?? "S256";
321
+
322
+ return (ctx) =>
323
+ runCeremonyHandler(
324
+ async () => {
325
+ for (const key of Object.keys(options.authorizeParams ?? {})) {
326
+ if (OAUTH2_PROXIED_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())) {
327
+ throw new ValidationError(
328
+ `OAuth2 proxied authorizeParams cannot override reserved parameter "${key}".`,
329
+ );
330
+ }
331
+ }
332
+ const proxyOrigin = ctx.env.get(OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY);
333
+ if (!proxyOrigin) {
334
+ throw new ProviderSecretError(
335
+ `Missing required platform environment: ${OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY}`,
336
+ );
337
+ }
338
+ const clientId = getRequiredEnv(ctx, options.clientIdEnvKey);
339
+ if (!ctx.flowId) {
340
+ throw new ValidationError("OAuth2 proxied start requires the gateway flow id.");
341
+ }
342
+
343
+ const authorizePath = new URL(options.authorizeUrl).pathname;
344
+ const url = new URL(
345
+ `/p/${encodeURIComponent(ctx.providerId)}/f/${encodeURIComponent(ctx.flowId)}${authorizePath}`,
346
+ proxyOrigin,
347
+ );
348
+ url.searchParams.set("client_id", clientId);
349
+ url.searchParams.set("response_type", "code");
350
+
351
+ if (pkce === "S256") {
352
+ const verifier = createCodeVerifier();
353
+ ctx.context.set(OAUTH2_PROXIED_PKCE_VERIFIER_KEY, verifier);
354
+ url.searchParams.set("code_challenge", createCodeChallenge(verifier));
355
+ url.searchParams.set("code_challenge_method", "S256");
356
+ }
357
+
358
+ for (const [key, value] of Object.entries(options.authorizeParams ?? {})) {
359
+ url.searchParams.set(key, value);
360
+ }
361
+
362
+ return createTurn("redirect", {
363
+ data: { url: url.toString() },
364
+ hint: "Open the provider authorization page to continue.",
365
+ expectedInput: {
366
+ type: "object",
367
+ required: ["code", "state"],
368
+ properties: {
369
+ code: { type: "string" },
370
+ state: { type: "string" },
371
+ },
372
+ },
373
+ });
374
+ },
375
+ "OAuth2 proxied start failed",
376
+ ctx,
377
+ );
378
+ }
379
+
280
380
  export function createDeviceFlowCeremony(options: {
281
381
  deviceCodeUrl: string;
282
382
  tokenUrl: string;
@@ -329,26 +429,19 @@ export function createDeviceFlowCeremony(options: {
329
429
  client_id: getRequiredEnv(ctx, options.clientIdEnvKey),
330
430
  ...(options.clientSecretEnvKey
331
431
  ? {
332
- client_secret: getRequiredEnv(
333
- ctx,
334
- options.clientSecretEnvKey,
335
- ),
432
+ client_secret: getRequiredEnv(ctx, options.clientSecretEnvKey),
336
433
  }
337
434
  : {}),
338
435
  });
339
436
  const data = ensureRecord(response.data);
340
437
  const errorCode = getString(data, "error");
341
438
 
342
- if (
343
- errorCode === "authorization_pending" ||
344
- errorCode === "slow_down"
345
- ) {
439
+ if (errorCode === "authorization_pending" || errorCode === "slow_down") {
346
440
  return createTurn("poll", {
347
441
  data,
348
442
  hint: "Authorization pending.",
349
443
  timing: {
350
- suggestedPollIntervalMs:
351
- errorCode === "slow_down" ? 10_000 : 5_000,
444
+ suggestedPollIntervalMs: errorCode === "slow_down" ? 10_000 : 5_000,
352
445
  maxWaitMs: 120_000,
353
446
  },
354
447
  });
@@ -367,9 +460,7 @@ export function createDeviceFlowCeremony(options: {
367
460
  ctx,
368
461
  ),
369
462
  abort: async () =>
370
- validateCeremonyOutput(
371
- createTurn("abort", { hint: "Device flow aborted." }),
372
- ),
463
+ validateCeremonyOutput(createTurn("abort", { hint: "Device flow aborted." })),
373
464
  };
374
465
  }
375
466
 
@@ -437,9 +528,7 @@ export function createWebAuthnCeremony(options: {
437
528
  input,
438
529
  ),
439
530
  abort: async () =>
440
- validateCeremonyOutput(
441
- createTurn("abort", { hint: "WebAuthn ceremony aborted." }),
442
- ),
531
+ validateCeremonyOutput(createTurn("abort", { hint: "WebAuthn ceremony aborted." })),
443
532
  };
444
533
  }
445
534
 
@@ -526,9 +615,7 @@ export function createMagicLinkCeremony(options: {
526
615
  ctx,
527
616
  ),
528
617
  abort: async () =>
529
- validateCeremonyOutput(
530
- createTurn("abort", { hint: "Magic link flow aborted." }),
531
- ),
618
+ validateCeremonyOutput(createTurn("abort", { hint: "Magic link flow aborted." })),
532
619
  };
533
620
  }
534
621
 
@@ -548,7 +635,7 @@ export function createFormCeremony(options: {
548
635
  continue: (ctx, input = {}) =>
549
636
  runCeremonyHandler(
550
637
  async () => {
551
- const { prevalidate } = await import("../runtime/prevalidate");
638
+ const { prevalidate } = await import("../runtime/prevalidate.js");
552
639
  const result = prevalidate(options.schema, input);
553
640
  if (!result.valid) {
554
641
  throw new ValidationError("Form input failed validation.", {
@@ -558,9 +645,7 @@ export function createFormCeremony(options: {
558
645
 
559
646
  return createTurn("complete", {
560
647
  data: {
561
- credential: options.mapCredential
562
- ? options.mapCredential(input)
563
- : input,
648
+ credential: options.mapCredential ? options.mapCredential(input) : input,
564
649
  },
565
650
  hint: "Form completed.",
566
651
  });
@@ -570,15 +655,11 @@ export function createFormCeremony(options: {
570
655
  input,
571
656
  ),
572
657
  abort: async () =>
573
- validateCeremonyOutput(
574
- createTurn("abort", { hint: "Form ceremony aborted." }),
575
- ),
658
+ validateCeremonyOutput(createTurn("abort", { hint: "Form ceremony aborted." })),
576
659
  };
577
660
  }
578
661
 
579
- export function combineCeremonies(
580
- ...ceremonies: AuthFlowDefinition[]
581
- ): AuthFlowDefinition {
662
+ export function combineCeremonies(...ceremonies: AuthFlowDefinition[]): AuthFlowDefinition {
582
663
  function getStage(ctx: FlowContext): number {
583
664
  const rawStage = ctx.context.get(COMBINED_STAGE_KEY);
584
665
  return typeof rawStage === "number" ? rawStage : 0;
@@ -630,9 +711,7 @@ export function combineCeremonies(
630
711
  async () => {
631
712
  const current = ceremonies[getStage(ctx)];
632
713
  if (!current?.poll) {
633
- throw new ValidationError(
634
- "Current ceremony does not support polling.",
635
- );
714
+ throw new ValidationError("Current ceremony does not support polling.");
636
715
  }
637
716
  return await current.poll(ctx);
638
717
  },
@@ -680,9 +759,7 @@ export function createSwitchCeremony(options: {
680
759
  async () => {
681
760
  const storedChoice = ctx.context.get(SWITCH_SELECTION_KEY);
682
761
  const choice =
683
- typeof storedChoice === "string"
684
- ? storedChoice
685
- : getString(input, "choice");
762
+ typeof storedChoice === "string" ? storedChoice : getString(input, "choice");
686
763
 
687
764
  if (!choice || !options.choices[choice]) {
688
765
  throw new ValidationError("A valid choice is required.");
@@ -710,9 +787,7 @@ export function createSwitchCeremony(options: {
710
787
 
711
788
  const ceremony = options.choices[choice];
712
789
  if (!ceremony?.poll) {
713
- throw new ValidationError(
714
- "Selected ceremony does not support polling.",
715
- );
790
+ throw new ValidationError("Selected ceremony does not support polling.");
716
791
  }
717
792
 
718
793
  return await ceremony.poll(ctx);
@@ -2,6 +2,7 @@ export type ApifuseCommandName =
2
2
  | "create"
3
3
  | "dev"
4
4
  | "check"
5
+ | "sync-assets"
5
6
  | "submit-check"
6
7
  | "bounty-check"
7
8
  | "record"
@@ -46,6 +47,14 @@ export const COMMAND_MANIFEST: Record<
46
47
  examples: ["apifuse check .", "apifuse check providers/korea-air-quality"],
47
48
  modulePath: "./apifuse-check",
48
49
  },
50
+ "sync-assets": {
51
+ name: "sync-assets",
52
+ summary:
53
+ "Regenerate SDK-managed agent prompt assets (AGENTS.md, .agents/skills, symlinks, manifest) for the installed SDK version.",
54
+ usage: "apifuse sync-assets [path] [--check]",
55
+ examples: ["apifuse sync-assets .", "apifuse sync-assets . --check"],
56
+ modulePath: "./apifuse-sync-assets",
57
+ },
49
58
  "submit-check": {
50
59
  name: "submit-check",
51
60
  summary:
@@ -103,6 +112,7 @@ export const COMMAND_ORDER: ApifuseCommandName[] = [
103
112
  "create",
104
113
  "dev",
105
114
  "check",
115
+ "sync-assets",
106
116
  "submit-check",
107
117
  "record",
108
118
  "test",
package/src/cli/create.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
- import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises";
4
4
  import { dirname, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
@@ -8,6 +8,11 @@ import { cancel, intro, isCancel, note, outro, select, text } from "@clack/promp
8
8
  import { z } from "zod";
9
9
 
10
10
  import packageJson from "../../package.json";
11
+ import {
12
+ buildPromptAssetManifest,
13
+ buildPromptAssetPlanEntries,
14
+ PROMPT_ASSET_MANIFEST_PATH,
15
+ } from "./prompt-assets.js";
11
16
 
12
17
  export const PROVIDER_NAME_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
13
18
  export const CATEGORY_OPTIONS = [
@@ -64,7 +69,10 @@ export type CreateResolvedOptions = {
64
69
 
65
70
  export type ProviderPlanFile = {
66
71
  path: string;
72
+ /** File content, or the symlink target (relative path) for kind "symlink". */
67
73
  content: string;
74
+ /** Absent kind means "file" (backward compatible with older consumers). */
75
+ kind?: "file" | "symlink";
68
76
  };
69
77
 
70
78
  export type ProviderCreatePlan = {
@@ -410,11 +418,16 @@ export async function buildProviderCreatePlan(
410
418
  );
411
419
  }
412
420
 
421
+ // Exact pin, not a caret range: the prompt-asset freshness gate requires
422
+ // manifest.sdkVersion === installed SDK version, and the manifest records
423
+ // this CLI's own version. A caret range would let `bun install` resolve a
424
+ // newer SDK than the create binary, making a fresh scaffold fail its own
425
+ // gate. `sync-assets` after an explicit SDK upgrade re-records the version.
413
426
  const sdkSpecifier =
414
427
  options.sdkSpecifier ??
415
428
  (options.preset === "monorepo" && resolvedWorkspaceRoot
416
429
  ? "workspace:*"
417
- : `^${packageJson.version}`);
430
+ : packageJson.version);
418
431
  const relativeProviderRoot = relative(cwd, providerRoot) || options.name;
419
432
  const nextDevCommand = `cd ${relativeProviderRoot} && bun run dev`;
420
433
  const packageName =
@@ -538,40 +551,23 @@ export async function buildProviderCreatePlan(
538
551
  PROVIDER_ID: options.name,
539
552
  }),
540
553
  },
541
- {
542
- path: resolve(providerRoot, "AGENTS.md"),
543
- content: await renderTemplate("AGENTS.md.tpl", {}),
544
- },
545
- {
546
- path: resolve(providerRoot, "CLAUDE.md"),
547
- content: await renderTemplate("CLAUDE.md.tpl", {}),
548
- },
549
- {
550
- path: resolve(providerRoot, "skills", "normalization-standards", "SKILL.md"),
551
- content: await renderTemplate("skills/normalization-standards/SKILL.md.tpl", {}),
552
- },
553
- {
554
- path: resolve(providerRoot, "skills", "upstream-contract-verification", "SKILL.md"),
555
- content: await renderTemplate("skills/upstream-contract-verification/SKILL.md.tpl", {}),
556
- },
557
- {
558
- path: resolve(providerRoot, "skills", "fixtures-and-recording", "SKILL.md"),
559
- content: await renderTemplate("skills/fixtures-and-recording/SKILL.md.tpl", {}),
560
- },
561
- {
562
- path: resolve(providerRoot, "skills", "pagination-and-counts", "SKILL.md"),
563
- content: await renderTemplate("skills/pagination-and-counts/SKILL.md.tpl", {}),
564
- },
565
- {
566
- path: resolve(providerRoot, "skills", "health-checks-and-fail-closed", "SKILL.md"),
567
- content: await renderTemplate("skills/health-checks-and-fail-closed/SKILL.md.tpl", {}),
568
- },
569
- {
570
- path: resolve(providerRoot, "skills", "upstream-notes", "README.md"),
571
- content: await renderTemplate("skills/upstream-notes/README.md.tpl", {}),
572
- },
573
554
  ];
574
555
 
556
+ const promptAssetEntries = await buildPromptAssetPlanEntries(renderTemplate);
557
+ for (const entry of promptAssetEntries) {
558
+ files.push({
559
+ path: resolve(providerRoot, entry.path),
560
+ content: entry.content,
561
+ ...(entry.kind === "symlink" ? { kind: "symlink" as const } : {}),
562
+ });
563
+ }
564
+ // Manifest last: writePlan writes in order, so a crash mid-scaffold never
565
+ // leaves a manifest that claims assets which were not written.
566
+ files.push({
567
+ path: resolve(providerRoot, PROMPT_ASSET_MANIFEST_PATH),
568
+ content: buildPromptAssetManifest(promptAssetEntries, packageJson.version),
569
+ });
570
+
575
571
  return {
576
572
  displayName: options.displayName,
577
573
  files,
@@ -614,6 +610,7 @@ function renderPackageJson(input: { packageName: string; sdkSpecifier: string })
614
610
  check: "apifuse check . && bun run type-check",
615
611
  "type-check": "tsc --noEmit",
616
612
  "submit-check": "apifuse submit-check . --markdown submission-report.md",
613
+ "sync-assets": "apifuse sync-assets .",
617
614
  test: "apifuse test .",
618
615
  record: "apifuse record .",
619
616
  start: "bun start.ts",
@@ -869,6 +866,13 @@ function isApifuseInternalWorkspaceRoot(workspaceRoot: string): boolean {
869
866
  async function writePlan(plan: ProviderCreatePlan): Promise<void> {
870
867
  for (const file of plan.files) {
871
868
  await mkdir(dirname(file.path), { recursive: true });
869
+ if (file.kind === "symlink") {
870
+ // rm operates on the link itself (lstat semantics) and tolerates
871
+ // directories, so whatever occupies the path is replaced by the link.
872
+ await rm(file.path, { recursive: true, force: true });
873
+ await symlink(file.content, file.path);
874
+ continue;
875
+ }
872
876
  await writeFile(file.path, file.content);
873
877
  }
874
878
  }
@@ -940,7 +944,10 @@ function printResult(plan: ProviderCreatePlan, jsonMode: boolean, dryRun: boolea
940
944
  },
941
945
  validationCommands: plan.validationCommands,
942
946
  nextDevCommand: plan.nextDevCommand,
943
- files: plan.files.map((file) => relative(plan.providerRoot, file.path) || file.path),
947
+ files: plan.files.map((file) => {
948
+ const relativePath = relative(plan.providerRoot, file.path) || file.path;
949
+ return file.kind === "symlink" ? `${relativePath} -> ${file.content} (symlink)` : relativePath;
950
+ }),
944
951
  };
945
952
 
946
953
  if (jsonMode) {