@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
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import {
4
+ formatPromptAssetIssues,
5
+ installedSdkVersion,
6
+ syncPromptAssets,
7
+ verifyPromptAssets,
8
+ } from "../src/cli/prompt-assets.js";
9
+ import { resolveProviderRoot } from "./apifuse-check.js";
10
+
11
+ const HELP_TEXT = `Usage: apifuse sync-assets [path] [--check]
12
+ Example: apifuse sync-assets .
13
+ Default: apifuse sync-assets .
14
+
15
+ Regenerates the SDK-managed agent prompt assets for the installed SDK version:
16
+ AGENTS.md, .agents/skills/**, the CLAUDE.md/.claude/.codex symlinks, and the
17
+ .apifuse/prompt-assets.json manifest. Legacy top-level skills/ layouts are
18
+ migrated. Idempotent.
19
+
20
+ Options:
21
+ --check Verify only; exit 1 with a diff list when assets are stale/missing/modified
22
+ --help, -h Show this help`;
23
+
24
+ export async function main() {
25
+ const args = normalizeArgs(process.argv.slice(2));
26
+
27
+ if (args.includes("--help") || args.includes("-h")) {
28
+ console.log(HELP_TEXT);
29
+ return;
30
+ }
31
+
32
+ let checkOnly = false;
33
+ let inputPath: string | undefined;
34
+ for (const arg of args) {
35
+ if (arg === "--check") {
36
+ checkOnly = true;
37
+ continue;
38
+ }
39
+ if (arg.startsWith("-")) {
40
+ throw new Error(`Unknown option: ${arg}`);
41
+ }
42
+ if (inputPath !== undefined) {
43
+ throw new Error(`Unexpected argument: ${arg}`);
44
+ }
45
+ inputPath = arg;
46
+ }
47
+
48
+ const providerRoot = resolveProviderRoot(inputPath ?? ".");
49
+
50
+ if (checkOnly) {
51
+ const verification = verifyPromptAssets(providerRoot);
52
+ if (verification.ok) {
53
+ console.log(
54
+ `Prompt assets are in sync with the installed SDK (${installedSdkVersion()}): ${providerRoot}`,
55
+ );
56
+ return;
57
+ }
58
+ console.error(`Prompt assets are out of sync in ${providerRoot}:`);
59
+ for (const issue of formatPromptAssetIssues(verification)) {
60
+ console.error(` - ${issue}`);
61
+ }
62
+ console.error("\nRun `bun run sync-assets` (or `bunx apifuse sync-assets .`) to regenerate.");
63
+ process.exit(1);
64
+ }
65
+
66
+ const result = syncPromptAssets(providerRoot);
67
+
68
+ // Honesty gate: writes alone never imply success. sync-assets intentionally
69
+ // PRESERVES (does not delete) unauthorized skills and symlinks under
70
+ // .agents/skills, so it can return changed:false while verify still fails.
71
+ // Re-verify AFTER writing and let the true post-sync state drive the exit
72
+ // code — never claim success while the freshness gate would reject the tree.
73
+ const verification = verifyPromptAssets(providerRoot);
74
+
75
+ if (result.changed) {
76
+ for (const removed of result.removed) {
77
+ console.log(`removed ${removed}`);
78
+ }
79
+ for (const wrote of result.wroteFiles) {
80
+ console.log(`wrote ${wrote}`);
81
+ }
82
+ for (const link of result.createdSymlinks) {
83
+ console.log(`symlink ${link}`);
84
+ }
85
+ console.log(`manifest ${result.manifestPath} (sdkVersion ${installedSdkVersion()})`);
86
+ }
87
+
88
+ if (!verification.ok) {
89
+ console.error(`\nPrompt assets are still out of sync in ${providerRoot}:`);
90
+ for (const issue of formatPromptAssetIssues(verification)) {
91
+ console.error(` - ${issue}`);
92
+ }
93
+ console.error(
94
+ "\nsync-assets never deletes unrecognized content: resolve these by hand — remove any unauthorized skill directory or symlink under .agents/skills/, migrate a real .claude/.codex directory into .agents/ — then re-run `apifuse sync-assets .`.",
95
+ );
96
+ process.exit(1);
97
+ }
98
+
99
+ if (!result.changed) {
100
+ console.log(
101
+ `Prompt assets already in sync with the installed SDK (${installedSdkVersion()}): ${providerRoot}`,
102
+ );
103
+ return;
104
+ }
105
+ console.log(`\nPrompt assets synced: ${providerRoot}`);
106
+ }
107
+
108
+ function normalizeArgs(argv: string[]): string[] {
109
+ return argv[0] === "sync-assets" ? argv.slice(1) : argv;
110
+ }
111
+
112
+ if (import.meta.main) {
113
+ await main().catch((error: unknown) => {
114
+ console.error(error instanceof Error ? error.message : String(error));
115
+ process.exit(1);
116
+ });
117
+ }
package/bin/apifuse.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import packageJson from "../package.json";
4
- import { COMMAND_MANIFEST, COMMAND_ORDER } from "../src/cli/commands";
4
+ import { COMMAND_MANIFEST, COMMAND_ORDER } from "../src/cli/commands.js";
5
5
 
6
6
  const command = process.argv[2];
7
7
 
@@ -0,0 +1,50 @@
1
+ const MIN_RECORDED_DELIMITED_TEXT_LENGTH = 200;
2
+ const MIN_RECORDED_DELIMITED_TEXT_LINES = 3;
3
+ const MAX_SAMPLED_DELIMITED_TEXT_LINES = 10;
4
+ const DELIMITER_CANDIDATES = [",", "\t", ";"] as const;
5
+
6
+ // Recognizes a recorded operation value that is a substantive tabular text
7
+ // payload. The length floor and repeated, quote-aware field structure keep
8
+ // short strings and prose from being treated as fixture provenance.
9
+ export function hasSubstantiveDelimitedTextStructure(value: string): boolean {
10
+ if (value.length < MIN_RECORDED_DELIMITED_TEXT_LENGTH) {
11
+ return false;
12
+ }
13
+
14
+ const lines = value
15
+ .replace(/^\uFEFF/, "")
16
+ .split(/\r\n?|\n/)
17
+ .filter((line) => line.trim().length > 0);
18
+ if (lines.length < MIN_RECORDED_DELIMITED_TEXT_LINES) {
19
+ return false;
20
+ }
21
+
22
+ const sampledLines = lines.slice(0, MAX_SAMPLED_DELIMITED_TEXT_LINES);
23
+ return DELIMITER_CANDIDATES.some((delimiter) => {
24
+ const fieldCounts = sampledLines.map((line) => countDelimitedFields(line, delimiter));
25
+ const expectedFieldCount = fieldCounts[0];
26
+ return (
27
+ expectedFieldCount !== undefined &&
28
+ expectedFieldCount >= 2 &&
29
+ fieldCounts.every((fieldCount) => fieldCount === expectedFieldCount)
30
+ );
31
+ });
32
+ }
33
+
34
+ function countDelimitedFields(line: string, delimiter: string): number | undefined {
35
+ let fieldCount = 1;
36
+ let insideQuotes = false;
37
+ for (let index = 0; index < line.length; index += 1) {
38
+ const character = line[index];
39
+ if (character === '"') {
40
+ if (insideQuotes && line[index + 1] === '"') {
41
+ index += 1;
42
+ } else {
43
+ insideQuotes = !insideQuotes;
44
+ }
45
+ } else if (!insideQuotes && character === delimiter) {
46
+ fieldCount += 1;
47
+ }
48
+ }
49
+ return insideQuotes ? undefined : fieldCount;
50
+ }
@@ -14,7 +14,7 @@ import {
14
14
  normalizedXmlName,
15
15
  rootXmlContext,
16
16
  type XmlSemanticBranch,
17
- } from "./submit-check-xml-semantics";
17
+ } from "./submit-check-xml-semantics.js";
18
18
 
19
19
  const MIN_RECORDED_XML_LENGTH = 128;
20
20
  // Recorded fixtures must remain reviewable; this pre-allocation cap also bounds the parser tree.
@@ -1,4 +1,4 @@
1
- import type { AuthTurn } from "../types";
1
+ import type { AuthTurn } from "../types.js";
2
2
  export type { AuthTurn };
3
3
  /**
4
4
  * Major version of the AuthTurn envelope contract.
@@ -20,7 +20,7 @@ export declare const AUTH_TURN_SCHEMA_ARTIFACT_PATH = "dist/auth-turn/auth-turn.
20
20
  *
21
21
  * This is the exact codification of the runtime validation the SDK applies to
22
22
  * ceremony outputs (see `validateCeremonyOutput` in `src/ceremonies`), which
23
- * compiles this same document. `kind` is an OPEN string on the wire: the known
23
+ * evaluates this same document. `kind` is an OPEN string on the wire: the known
24
24
  * kinds in {@link TURN_KINDS} are tooling metadata, never a wire constraint.
25
25
  *
26
26
  * The committed artifact at `src/auth-turn/auth-turn.v1.schema.json` (shipped
@@ -87,7 +87,7 @@ export declare const AUTH_TURN_SCHEMA: {
87
87
  };
88
88
  readonly $defs: {
89
89
  readonly completeTurnData: {
90
- readonly title: 'Terminal payload for kind "complete"';
90
+ readonly title: "Terminal payload for kind \"complete\"";
91
91
  readonly description: "data payload of a complete turn. The gateway extracts data.credential for persistence; complete turns are never echoed to browsers.";
92
92
  readonly type: "object";
93
93
  readonly additionalProperties: true;
@@ -104,7 +104,7 @@ export declare const AUTH_TURN_SCHEMA: {
104
104
  };
105
105
  };
106
106
  readonly abortTurnData: {
107
- readonly title: 'Terminal payload for kind "abort"';
107
+ readonly title: "Terminal payload for kind \"abort\"";
108
108
  readonly description: "data payload of an abort turn. code, when present, is the machine-readable abort reason.";
109
109
  readonly type: "object";
110
110
  readonly additionalProperties: true;
@@ -18,7 +18,7 @@ export const AUTH_TURN_SCHEMA_ARTIFACT_PATH = "dist/auth-turn/auth-turn.v1.schem
18
18
  *
19
19
  * This is the exact codification of the runtime validation the SDK applies to
20
20
  * ceremony outputs (see `validateCeremonyOutput` in `src/ceremonies`), which
21
- * compiles this same document. `kind` is an OPEN string on the wire: the known
21
+ * evaluates this same document. `kind` is an OPEN string on the wire: the known
22
22
  * kinds in {@link TURN_KINDS} are tooling metadata, never a wire constraint.
23
23
  *
24
24
  * The committed artifact at `src/auth-turn/auth-turn.v1.schema.json` (shipped
package/dist/auth.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { AuthError } from "./errors";
2
- import type { AuthConfig, AuthFlowTerminalContext, AuthTurn, ContextDeclaration, CredentialDeclaration, FlowContext, ProviderLocaleKeyInput } from "./types";
1
+ import { AuthError } from "./errors.js";
2
+ import type { AuthConfig, AuthFlowTerminalContext, AuthTurn, ContextDeclaration, CredentialDeclaration, FlowContext, ProviderLocaleKeyInput } from "./types.js";
3
3
  export type CredentialsAuthFieldType = "string" | "email" | "password" | "otp";
4
4
  export interface CredentialsAuthField {
5
5
  type?: CredentialsAuthFieldType;
@@ -66,6 +66,20 @@ export interface DefineCredentialsAuthOptions<TFields extends CredentialsAuthFie
66
66
  /** Extra auth-flow context keys used by custom login/challenge code. */
67
67
  contextKeys?: readonly string[];
68
68
  login(ctx: FlowContext, input: CredentialsAuthInput<TFields>): CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string> | Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
69
+ /**
70
+ * Optional re-mint of an expired session from the stored credential, wired
71
+ * to `auth.flow.refresh`.
72
+ *
73
+ * Credential-auth upstreams routinely invalidate a session well before the
74
+ * `expiresAt` the provider advertised, which leaves every operation failing
75
+ * with a reauth error until a human repeats the whole interactive login.
76
+ * Implement this to re-establish the session from what is already stored on
77
+ * the connection; the result is resolved exactly like `login`, so it may
78
+ * also raise a challenge when the upstream demands one. Omit it when the
79
+ * upstream has no non-interactive path and re-authentication genuinely
80
+ * requires the user.
81
+ */
82
+ refresh?(ctx: FlowContext, input: Partial<CredentialsAuthInput<TFields>>): CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string> | Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
69
83
  }
70
84
  export interface DefinedCredentialsAuth {
71
85
  auth: AuthConfig;
package/dist/auth.js CHANGED
@@ -1,4 +1,5 @@
1
- import { AuthError, ProviderError } from "./errors";
1
+ import { AuthError, ProviderError } from "./errors.js";
2
+ import { declarationInvalidError, DECLARATION_RULE_IDS, } from "./declaration-validation.js";
2
3
  const CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY = "__credentialsAuthChallenge";
3
4
  const DEFAULT_COMPLETE_TURN_ID = "auth.complete";
4
5
  const DEFAULT_ABORT_TURN_ID = "auth.abort";
@@ -97,7 +98,7 @@ export function createAuthFlowHelpers(options = {}) {
97
98
  },
98
99
  };
99
100
  },
100
- abort({ code, message, retry, actionHint, fieldErrors, data, turnId, expiresAt, }) {
101
+ abort({ code, message, retry, actionHint, fieldErrors, data, turnId, expiresAt }) {
101
102
  return {
102
103
  kind: "abort",
103
104
  ...authTurnBase({ turnId, defaultTurnId: DEFAULT_ABORT_TURN_ID, expiresAt }),
@@ -169,12 +170,8 @@ function expectedInputFromFields(fields) {
169
170
  ...(field.type === "password" ? { format: "password" } : {}),
170
171
  ...(field.type === "otp" ? { format: "otp" } : {}),
171
172
  ...(field.labelKey ? { nameKey: field.labelKey } : {}),
172
- ...(field.descriptionKey
173
- ? { descriptionKey: field.descriptionKey }
174
- : {}),
175
- ...(field.placeholderKey
176
- ? { placeholderKey: field.placeholderKey }
177
- : {}),
173
+ ...(field.descriptionKey ? { descriptionKey: field.descriptionKey } : {}),
174
+ ...(field.placeholderKey ? { placeholderKey: field.placeholderKey } : {}),
178
175
  ...(field.sensitive || field.type === "password" || field.type === "otp"
179
176
  ? { sensitive: true }
180
177
  : {}),
@@ -202,6 +199,23 @@ function normalizeInput(fields, input) {
202
199
  }
203
200
  return result;
204
201
  }
202
+ /**
203
+ * Refresh variant of {@link normalizeInput}. `login` coerces every declared
204
+ * field to a string because the interactive turn has already enforced that they
205
+ * are present; refresh runs with no user present, so an absent field is omitted
206
+ * rather than turned into an empty string. That keeps "the user did not supply
207
+ * this" distinguishable from "the user supplied an empty value".
208
+ */
209
+ function normalizePartialInput(fields, input) {
210
+ const result = {};
211
+ for (const name of Object.keys(fields)) {
212
+ const value = input?.[name];
213
+ if (typeof value === "string") {
214
+ result[name] = value;
215
+ }
216
+ }
217
+ return result;
218
+ }
205
219
  function assertCredentialKeys(credentialKeys, credential) {
206
220
  const missing = credentialKeys.filter((key) => {
207
221
  const value = credential[key];
@@ -291,17 +305,15 @@ function completeTurn(credentialKeys, result, defaultTurnId) {
291
305
  };
292
306
  }
293
307
  function challengeTurn(definition, request) {
294
- const expectedInput = definition.fields
295
- ? expectedInputFromFields(definition.fields)
296
- : undefined;
308
+ const expectedInput = definition.fields ? expectedInputFromFields(definition.fields) : undefined;
297
309
  return {
298
310
  kind: expectedInput ? "form" : "pending",
299
311
  turnId: request.turnId ?? definition.turnId ?? `credentials.${request.challengeId}`,
300
312
  ...(request.expiresAt ? { expiresAt: request.expiresAt } : {}),
301
- ...(request.hintKey ?? definition.hintKey
313
+ ...((request.hintKey ?? definition.hintKey)
302
314
  ? { hintKey: request.hintKey ?? definition.hintKey }
303
315
  : {}),
304
- ...(request.timing ?? definition.timing
316
+ ...((request.timing ?? definition.timing)
305
317
  ? { timing: request.timing ?? definition.timing }
306
318
  : {}),
307
319
  ...(expectedInput ? { expectedInput } : {}),
@@ -378,13 +390,14 @@ async function pollPendingChallenge(ctx, credentialKeys, challenges, pending, co
378
390
  export function defineCredentialsAuth(options) {
379
391
  if (Object.keys(options.fields).length === 0) {
380
392
  throw new ProviderError("defineCredentialsAuth requires at least one field", {
381
- fix: "Pass fields such as { email: { type: \"email\" }, password: { type: \"password\" } }.",
393
+ fix: 'Pass fields such as { email: { type: "email" }, password: { type: "password" } }.',
382
394
  });
383
395
  }
384
396
  const expectedInput = expectedInputFromFields(options.fields);
385
397
  const retryTurnId = options.retryTurnId ?? "credentials.retry";
386
398
  const completeTurnId = options.completeTurnId ?? "credentials.complete";
387
399
  const challenges = (options.challenges ?? {});
400
+ validateCredentialsAuthChallenges(challenges);
388
401
  return {
389
402
  auth: {
390
403
  mode: "credentials",
@@ -417,6 +430,27 @@ export function defineCredentialsAuth(options) {
417
430
  }
418
431
  return await pollPendingChallenge(ctx, options.credentialKeys, challenges, pending, completeTurnId);
419
432
  },
433
+ // Only advertise refresh when the provider implements it: the
434
+ // protocol treats the hook's presence as "this connection can be
435
+ // re-established without the user", and exposing a stub that
436
+ // cannot actually re-mint would turn a recoverable expiry into a
437
+ // silent failure.
438
+ ...(options.refresh
439
+ ? {
440
+ refresh: async (ctx, rawInput) => {
441
+ // A pending challenge belongs to the interactive flow that
442
+ // raised it; finish it there rather than restarting.
443
+ const pending = getPendingChallenge(ctx);
444
+ if (pending) {
445
+ return await continuePendingChallenge(ctx, options.credentialKeys, challenges, pending, rawInput, completeTurnId);
446
+ }
447
+ // Refresh runs without user input, so fields are optional
448
+ // here — unlike `continue`, missing ones are not a retry.
449
+ const result = await options.refresh(ctx, normalizePartialInput(options.fields, rawInput));
450
+ return await resolveAuthResult(ctx, options.credentialKeys, challenges, result, completeTurnId);
451
+ },
452
+ }
453
+ : {}),
420
454
  },
421
455
  },
422
456
  credential: {
@@ -427,10 +461,34 @@ export function defineCredentialsAuth(options) {
427
461
  ...(options.justification ? { justification: options.justification } : {}),
428
462
  },
429
463
  context: {
430
- keys: Array.from(new Set([
431
- CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY,
432
- ...(options.contextKeys ?? []),
433
- ])),
464
+ keys: Array.from(new Set([CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY, ...(options.contextKeys ?? [])])),
434
465
  },
435
466
  };
436
467
  }
468
+ function validateCredentialsAuthChallenges(challenges) {
469
+ const violations = [];
470
+ for (const [challengeId, challenge] of Object.entries(challenges)) {
471
+ const fieldCount = challenge.fields && typeof challenge.fields === "object"
472
+ ? Object.keys(challenge.fields).length
473
+ : 0;
474
+ const fieldsDeclared = challenge.fields !== undefined && challenge.fields !== null;
475
+ const hasFields = fieldCount > 0;
476
+ const hasVerify = typeof challenge.verify === "function";
477
+ const hasPoll = typeof challenge.poll === "function";
478
+ const isInteractive = hasFields && hasVerify && !hasPoll;
479
+ const isPolling = !fieldsDeclared && !hasVerify && hasPoll;
480
+ const isHybrid = hasFields && hasVerify && hasPoll;
481
+ const emptyFieldsDeclared = fieldsDeclared && !hasFields;
482
+ if (!emptyFieldsDeclared && (isInteractive || isPolling || isHybrid))
483
+ continue;
484
+ const path = `challenges.${challengeId}`;
485
+ violations.push({
486
+ ruleId: DECLARATION_RULE_IDS.challengeShape,
487
+ path,
488
+ message: "challenge must be interactive, polling, or an explicit hybrid.",
489
+ fix: `Give ${path} non-empty fields plus verify, poll alone, or all three for a hybrid.`,
490
+ });
491
+ }
492
+ if (violations.length > 0)
493
+ throw declarationInvalidError(violations);
494
+ }
@@ -1,5 +1,7 @@
1
- import type { AuthFlowDefinition, AuthTurn } from "../types";
1
+ import type { AuthFlowDefinition, AuthTurn } from "../types.js";
2
2
  type JsonObject = Record<string, unknown>;
3
+ export declare const OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY = "APIFUSE__AUTH_PROXY__URL";
4
+ export declare const OAUTH2_PROXIED_PKCE_VERIFIER_KEY = "__oauth2_proxied_pkce_verifier";
3
5
  export declare function validateCeremonyOutput(turn: unknown): AuthTurn;
4
6
  export declare function createOAuth2Ceremony(options: {
5
7
  authorizeUrl: string;
@@ -9,6 +11,12 @@ export declare function createOAuth2Ceremony(options: {
9
11
  scopes: string[];
10
12
  usePKCE?: boolean;
11
13
  }): AuthFlowDefinition;
14
+ /**
15
+ * Builds the start handler for a custom-scheme OAuth provider. The shared
16
+ * auth-proxy owns state minting and callback capture, while the provider owns
17
+ * token exchange and any following authentication turns.
18
+ */
19
+ export declare function createOAuth2ProxiedStart(options: import("../types.js").ProxiedOAuthConfig): import("../types.js").AuthFlowStartHandler;
12
20
  export declare function createDeviceFlowCeremony(options: {
13
21
  deviceCodeUrl: string;
14
22
  tokenUrl: string;
@@ -1,14 +1,17 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
- import Ajv2020 from "ajv/dist/2020.js";
3
- import { AUTH_TURN_SCHEMA } from "../auth-turn";
4
- import { FlowExpiredError, ProviderSecretError, TurnValidationError, ValidationError, } from "../errors";
5
- const ajv = new Ajv2020({ allErrors: true, strict: true, strictSchema: true });
6
- // Runtime ceremony-output validation derives from the exported versioned
7
- // contract: it compiles the exact AUTH_TURN_SCHEMA document shipped at
8
- // dist/auth-turn/auth-turn.v1.schema.json, so the two cannot drift.
9
- const validateAuthTurn = ajv.compile(AUTH_TURN_SCHEMA);
2
+ import { AUTH_TURN_SCHEMA } from "../auth-turn/index.js";
3
+ import { FlowExpiredError, ProviderSecretError, TurnValidationError, ValidationError, } from "../errors.js";
10
4
  const OAUTH2_STATE_KEY = "__oauth2_state";
11
5
  const OAUTH2_PKCE_VERIFIER_KEY = "__oauth2_pkce_verifier";
6
+ export const OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY = "APIFUSE__AUTH_PROXY__URL";
7
+ export const OAUTH2_PROXIED_PKCE_VERIFIER_KEY = "__oauth2_proxied_pkce_verifier";
8
+ const OAUTH2_PROXIED_RESERVED_AUTHORIZE_PARAMS = new Set([
9
+ "client_id",
10
+ "response_type",
11
+ "state",
12
+ "code_challenge",
13
+ "code_challenge_method",
14
+ ]);
12
15
  const DEVICE_FLOW_KEY = "__device_flow";
13
16
  const MAGIC_LINK_KEY = "__magic_link";
14
17
  const COMBINED_STAGE_KEY = "__combined_stage";
@@ -17,6 +20,50 @@ const FORM_FIELD_ORDER_EXTENSION = "x-apifuse-field-order";
17
20
  function isRecord(value) {
18
21
  return !!value && typeof value === "object" && !Array.isArray(value);
19
22
  }
23
+ function validateSchemaNode(value, schema, path, errors) {
24
+ if (schema.type === "object") {
25
+ if (!isRecord(value)) {
26
+ errors.push(`${path} must be object`);
27
+ return;
28
+ }
29
+ const properties = schema.properties ?? {};
30
+ for (const required of schema.required ?? []) {
31
+ if (!Object.hasOwn(value, required) || value[required] === undefined) {
32
+ errors.push(`${path} must have required property '${required}'`);
33
+ }
34
+ }
35
+ if (schema.additionalProperties === false) {
36
+ for (const key of Object.keys(value)) {
37
+ if (!Object.hasOwn(properties, key)) {
38
+ errors.push(`${path} must NOT have additional property '${key}'`);
39
+ }
40
+ }
41
+ }
42
+ for (const [key, childSchema] of Object.entries(properties)) {
43
+ if (Object.hasOwn(value, key) && value[key] !== undefined) {
44
+ validateSchemaNode(value[key], childSchema, `${path}/${key}`, errors);
45
+ }
46
+ }
47
+ return;
48
+ }
49
+ if (schema.type === "string") {
50
+ if (typeof value !== "string") {
51
+ errors.push(`${path} must be string`);
52
+ }
53
+ else if (schema.minLength !== undefined && value.length < schema.minLength) {
54
+ errors.push(`${path} must NOT have fewer than ${schema.minLength} characters`);
55
+ }
56
+ return;
57
+ }
58
+ if (schema.type === "number") {
59
+ if (typeof value !== "number" || !Number.isFinite(value)) {
60
+ errors.push(`${path} must be number`);
61
+ }
62
+ else if (schema.minimum !== undefined && value < schema.minimum) {
63
+ errors.push(`${path} must be >= ${schema.minimum}`);
64
+ }
65
+ }
66
+ }
20
67
  function ensureRecord(value) {
21
68
  return isRecord(value) ? value : {};
22
69
  }
@@ -38,11 +85,7 @@ function getRequiredEnv(ctx, key) {
38
85
  return value;
39
86
  }
40
87
  function toBase64Url(input) {
41
- return input
42
- .toString("base64")
43
- .replace(/\+/g, "-")
44
- .replace(/\//g, "_")
45
- .replace(/=+$/g, "");
88
+ return input.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
46
89
  }
47
90
  function createCodeVerifier() {
48
91
  return toBase64Url(randomBytes(32));
@@ -100,8 +143,7 @@ function withDeclaredFormFieldOrder(expectedInput) {
100
143
  return expectedInput;
101
144
  }
102
145
  const existingOrder = expectedInput[FORM_FIELD_ORDER_EXTENSION];
103
- if (Array.isArray(existingOrder) &&
104
- existingOrder.every((value) => typeof value === "string")) {
146
+ if (Array.isArray(existingOrder) && existingOrder.every((value) => typeof value === "string")) {
105
147
  return expectedInput;
106
148
  }
107
149
  return {
@@ -110,11 +152,14 @@ function withDeclaredFormFieldOrder(expectedInput) {
110
152
  };
111
153
  }
112
154
  export function validateCeremonyOutput(turn) {
113
- if (!validateAuthTurn(turn)) {
114
- const detail = validateAuthTurn.errors
115
- ?.map((error) => `${error.instancePath || "$"} ${error.message ?? "invalid"}`)
116
- .join("; ");
117
- throw new TurnValidationError(detail || "Invalid AuthTurn output");
155
+ // Evaluate the exact exported versioned schema without eagerly initializing
156
+ // a general-purpose JSON Schema compiler in every provider process. Contract
157
+ // parity tests compare this focused evaluator with AJV over all fixtures and
158
+ // edge probes, so the runtime and shipped document remain locked together.
159
+ const errors = [];
160
+ validateSchemaNode(turn, AUTH_TURN_SCHEMA, "", errors);
161
+ if (errors.length > 0) {
162
+ throw new TurnValidationError(errors.join("; "));
118
163
  }
119
164
  return turn;
120
165
  }
@@ -178,6 +223,54 @@ export function createOAuth2Ceremony(options) {
178
223
  abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "OAuth flow aborted." })),
179
224
  };
180
225
  }
226
+ /**
227
+ * Builds the start handler for a custom-scheme OAuth provider. The shared
228
+ * auth-proxy owns state minting and callback capture, while the provider owns
229
+ * token exchange and any following authentication turns.
230
+ */
231
+ export function createOAuth2ProxiedStart(options) {
232
+ const pkce = options.pkce ?? "S256";
233
+ return (ctx) => runCeremonyHandler(async () => {
234
+ for (const key of Object.keys(options.authorizeParams ?? {})) {
235
+ if (OAUTH2_PROXIED_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())) {
236
+ throw new ValidationError(`OAuth2 proxied authorizeParams cannot override reserved parameter "${key}".`);
237
+ }
238
+ }
239
+ const proxyOrigin = ctx.env.get(OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY);
240
+ if (!proxyOrigin) {
241
+ throw new ProviderSecretError(`Missing required platform environment: ${OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY}`);
242
+ }
243
+ const clientId = getRequiredEnv(ctx, options.clientIdEnvKey);
244
+ if (!ctx.flowId) {
245
+ throw new ValidationError("OAuth2 proxied start requires the gateway flow id.");
246
+ }
247
+ const authorizePath = new URL(options.authorizeUrl).pathname;
248
+ const url = new URL(`/p/${encodeURIComponent(ctx.providerId)}/f/${encodeURIComponent(ctx.flowId)}${authorizePath}`, proxyOrigin);
249
+ url.searchParams.set("client_id", clientId);
250
+ url.searchParams.set("response_type", "code");
251
+ if (pkce === "S256") {
252
+ const verifier = createCodeVerifier();
253
+ ctx.context.set(OAUTH2_PROXIED_PKCE_VERIFIER_KEY, verifier);
254
+ url.searchParams.set("code_challenge", createCodeChallenge(verifier));
255
+ url.searchParams.set("code_challenge_method", "S256");
256
+ }
257
+ for (const [key, value] of Object.entries(options.authorizeParams ?? {})) {
258
+ url.searchParams.set(key, value);
259
+ }
260
+ return createTurn("redirect", {
261
+ data: { url: url.toString() },
262
+ hint: "Open the provider authorization page to continue.",
263
+ expectedInput: {
264
+ type: "object",
265
+ required: ["code", "state"],
266
+ properties: {
267
+ code: { type: "string" },
268
+ state: { type: "string" },
269
+ },
270
+ },
271
+ });
272
+ }, "OAuth2 proxied start failed", ctx);
273
+ }
181
274
  export function createDeviceFlowCeremony(options) {
182
275
  return {
183
276
  start: (ctx) => runCeremonyHandler(async () => {
@@ -218,8 +311,7 @@ export function createDeviceFlowCeremony(options) {
218
311
  });
219
312
  const data = ensureRecord(response.data);
220
313
  const errorCode = getString(data, "error");
221
- if (errorCode === "authorization_pending" ||
222
- errorCode === "slow_down") {
314
+ if (errorCode === "authorization_pending" || errorCode === "slow_down") {
223
315
  return createTurn("poll", {
224
316
  data,
225
317
  hint: "Authorization pending.",
@@ -345,7 +437,7 @@ export function createFormCeremony(options) {
345
437
  return {
346
438
  start: async () => validateCeremonyOutput(buildJsonSchemaForm(options.schema, options.hint ?? "Provide the required input to continue.")),
347
439
  continue: (ctx, input = {}) => runCeremonyHandler(async () => {
348
- const { prevalidate } = await import("../runtime/prevalidate");
440
+ const { prevalidate } = await import("../runtime/prevalidate.js");
349
441
  const result = prevalidate(options.schema, input);
350
442
  if (!result.valid) {
351
443
  throw new ValidationError("Form input failed validation.", {
@@ -354,9 +446,7 @@ export function createFormCeremony(options) {
354
446
  }
355
447
  return createTurn("complete", {
356
448
  data: {
357
- credential: options.mapCredential
358
- ? options.mapCredential(input)
359
- : input,
449
+ credential: options.mapCredential ? options.mapCredential(input) : input,
360
450
  },
361
451
  hint: "Form completed.",
362
452
  });
@@ -428,9 +518,7 @@ export function createSwitchCeremony(options) {
428
518
  })),
429
519
  continue: (ctx, input = {}) => runCeremonyHandler(async () => {
430
520
  const storedChoice = ctx.context.get(SWITCH_SELECTION_KEY);
431
- const choice = typeof storedChoice === "string"
432
- ? storedChoice
433
- : getString(input, "choice");
521
+ const choice = typeof storedChoice === "string" ? storedChoice : getString(input, "choice");
434
522
  if (!choice || !options.choices[choice]) {
435
523
  throw new ValidationError("A valid choice is required.");
436
524
  }
@@ -1,4 +1,4 @@
1
- export type ApifuseCommandName = "create" | "dev" | "check" | "submit-check" | "bounty-check" | "record" | "test" | "perf";
1
+ export type ApifuseCommandName = "create" | "dev" | "check" | "sync-assets" | "submit-check" | "bounty-check" | "record" | "test" | "perf";
2
2
  export type ApifuseCommandManifest = {
3
3
  name: ApifuseCommandName;
4
4
  summary: string;