@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/AUTHORING.md +240 -0
  2. package/CHANGELOG.md +93 -0
  3. package/README.md +26 -10
  4. package/SUBMISSION.md +11 -12
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +36 -0
  9. package/bin/apifuse-pack-smoke.ts +22 -81
  10. package/bin/apifuse-pack-types.ts +266 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +53 -70
  13. package/bin/apifuse-submit-check.ts +2177 -353
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
  20. package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  21. package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  22. package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  23. package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  24. package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  25. package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  26. package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  27. package/dist/auth-turn/fixtures/valid/abort.json +8 -0
  28. package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
  29. package/dist/auth-turn/fixtures/valid/complete.json +13 -0
  30. package/dist/auth-turn/fixtures/valid/form.json +14 -0
  31. package/dist/auth-turn/fixtures/valid/message.json +13 -0
  32. package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
  33. package/dist/auth-turn/fixtures/valid/pending.json +5 -0
  34. package/dist/auth-turn/fixtures/valid/poll.json +9 -0
  35. package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
  36. package/dist/auth-turn/fixtures/valid/retry.json +8 -0
  37. package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  38. package/dist/auth-turn/index.d.ts +195 -0
  39. package/dist/auth-turn/index.js +133 -0
  40. package/dist/auth.d.ts +76 -0
  41. package/dist/auth.js +427 -0
  42. package/dist/ceremonies/index.d.ts +1 -1
  43. package/dist/ceremonies/index.js +14 -48
  44. package/dist/cli/commands.d.ts +1 -1
  45. package/dist/cli/commands.js +8 -0
  46. package/dist/cli/create.d.ts +3 -0
  47. package/dist/cli/create.js +47 -33
  48. package/dist/cli/prompt-assets.d.ts +80 -0
  49. package/dist/cli/prompt-assets.js +743 -0
  50. package/dist/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  51. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  52. package/dist/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
  53. package/dist/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  54. package/dist/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  55. package/dist/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
  56. package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
  57. package/dist/cli/templates/provider/.gitignore.tpl +22 -0
  58. package/dist/cli/templates/provider/AGENTS.md.tpl +96 -0
  59. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  60. package/dist/cli/templates/provider/README.md.tpl +163 -0
  61. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  62. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  63. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  64. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  65. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  66. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  67. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  68. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  69. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  70. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  71. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  72. package/dist/config/loader.d.ts +149 -8
  73. package/dist/config/loader.js +378 -89
  74. package/dist/contract-serialization.d.ts +2 -2
  75. package/dist/contract-serialization.js +3 -6
  76. package/dist/contract-types.d.ts +2 -2
  77. package/dist/contract.d.ts +3 -3
  78. package/dist/contract.js +5 -6
  79. package/dist/define.d.ts +13 -1
  80. package/dist/define.js +245 -178
  81. package/dist/dev.d.ts +1 -1
  82. package/dist/dev.js +1 -1
  83. package/dist/errors.d.ts +4 -1
  84. package/dist/errors.js +48 -0
  85. package/dist/i18n/catalog.d.ts +2 -2
  86. package/dist/i18n/catalog.js +4 -10
  87. package/dist/i18n/index.d.ts +2 -2
  88. package/dist/i18n/index.js +2 -2
  89. package/dist/i18n/keys.d.ts +2 -2
  90. package/dist/index.d.ts +44 -41
  91. package/dist/index.js +39 -36
  92. package/dist/lint.d.ts +1 -0
  93. package/dist/lint.js +35 -15
  94. package/dist/provider.d.ts +11 -9
  95. package/dist/provider.js +9 -8
  96. package/dist/public-schema-field-lint.d.ts +1 -1
  97. package/dist/recipes/gov-api.js +1 -1
  98. package/dist/runtime/auth-flow.d.ts +1 -1
  99. package/dist/runtime/auth-flow.js +4 -2
  100. package/dist/runtime/browser.d.ts +1 -1
  101. package/dist/runtime/browser.js +214 -25
  102. package/dist/runtime/cache.d.ts +1 -1
  103. package/dist/runtime/cache.js +4 -8
  104. package/dist/runtime/choice.d.ts +1 -1
  105. package/dist/runtime/choice.js +31 -35
  106. package/dist/runtime/credential.d.ts +1 -1
  107. package/dist/runtime/credential.js +1 -1
  108. package/dist/runtime/env.d.ts +1 -1
  109. package/dist/runtime/executor.d.ts +1 -1
  110. package/dist/runtime/executor.js +15 -3
  111. package/dist/runtime/http.d.ts +2 -2
  112. package/dist/runtime/http.js +160 -344
  113. package/dist/runtime/insights.d.ts +1 -1
  114. package/dist/runtime/insights.js +6 -13
  115. package/dist/runtime/instrumentation.d.ts +2 -2
  116. package/dist/runtime/instrumentation.js +56 -19
  117. package/dist/runtime/keyring.js +1 -1
  118. package/dist/runtime/namespace.js +1 -1
  119. package/dist/runtime/otlp.d.ts +1 -1
  120. package/dist/runtime/perf.d.ts +1 -1
  121. package/dist/runtime/provider.d.ts +1 -1
  122. package/dist/runtime/provider.js +1 -2
  123. package/dist/runtime/proxy-errors.d.ts +1 -1
  124. package/dist/runtime/proxy-errors.js +9 -7
  125. package/dist/runtime/proxy-nodemaven.d.ts +35 -0
  126. package/dist/runtime/proxy-nodemaven.js +128 -0
  127. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  128. package/dist/runtime/proxy-retry-policy.js +326 -0
  129. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  130. package/dist/runtime/proxy-telemetry.js +55 -52
  131. package/dist/runtime/redis.d.ts +1 -1
  132. package/dist/runtime/redis.js +2 -4
  133. package/dist/runtime/request-options.d.ts +1 -1
  134. package/dist/runtime/secrets.d.ts +27 -0
  135. package/dist/runtime/secrets.js +51 -0
  136. package/dist/runtime/state.d.ts +2 -2
  137. package/dist/runtime/state.js +15 -4
  138. package/dist/runtime/stealth.d.ts +7 -4
  139. package/dist/runtime/stealth.js +257 -215
  140. package/dist/runtime/stt.d.ts +1 -1
  141. package/dist/runtime/stt.js +11 -15
  142. package/dist/runtime/trace.d.ts +2 -2
  143. package/dist/runtime/trace.js +2 -4
  144. package/dist/runtime/waterfall.d.ts +1 -1
  145. package/dist/schema.d.ts +1 -1
  146. package/dist/schema.js +7 -15
  147. package/dist/serve.d.ts +1 -1
  148. package/dist/serve.js +1 -1
  149. package/dist/server/index.d.ts +7 -3
  150. package/dist/server/index.js +6 -2
  151. package/dist/server/self-test-input-tokens.d.ts +1 -0
  152. package/dist/server/self-test-input-tokens.js +37 -0
  153. package/dist/server/self-test-redaction.d.ts +20 -0
  154. package/dist/server/self-test-redaction.js +70 -0
  155. package/dist/server/self-test-token.d.ts +30 -0
  156. package/dist/server/self-test-token.js +50 -0
  157. package/dist/server/self-test.d.ts +199 -0
  158. package/dist/server/self-test.js +1113 -0
  159. package/dist/server/serve.d.ts +14 -3
  160. package/dist/server/serve.js +135 -64
  161. package/dist/server/types.d.ts +10 -9
  162. package/dist/server/types.js +3 -7
  163. package/dist/stealth/profiles.d.ts +1 -1
  164. package/dist/stealth/profiles.js +5 -14
  165. package/dist/stream.d.ts +1 -1
  166. package/dist/testing/index.d.ts +2 -2
  167. package/dist/testing/index.js +2 -2
  168. package/dist/testing/run.d.ts +1 -1
  169. package/dist/testing/run.js +12 -15
  170. package/dist/types.d.ts +237 -1
  171. package/dist/user-input.d.ts +30 -0
  172. package/dist/user-input.js +66 -0
  173. package/package.json +16 -5
  174. package/src/auth-turn/auth-turn.v1.schema.json +89 -0
  175. package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  176. package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  177. package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  178. package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  179. package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  180. package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  181. package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  182. package/src/auth-turn/fixtures/valid/abort.json +8 -0
  183. package/src/auth-turn/fixtures/valid/challenge.json +17 -0
  184. package/src/auth-turn/fixtures/valid/complete.json +13 -0
  185. package/src/auth-turn/fixtures/valid/form.json +14 -0
  186. package/src/auth-turn/fixtures/valid/message.json +13 -0
  187. package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
  188. package/src/auth-turn/fixtures/valid/pending.json +5 -0
  189. package/src/auth-turn/fixtures/valid/poll.json +9 -0
  190. package/src/auth-turn/fixtures/valid/redirect.json +16 -0
  191. package/src/auth-turn/fixtures/valid/retry.json +8 -0
  192. package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  193. package/src/auth-turn/index.ts +177 -0
  194. package/src/auth.ts +728 -0
  195. package/src/ceremonies/index.ts +33 -121
  196. package/src/cli/commands.ts +10 -0
  197. package/src/cli/create.ts +69 -99
  198. package/src/cli/prompt-assets.ts +865 -0
  199. package/src/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  200. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  201. package/src/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
  202. package/src/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  203. package/src/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  204. package/src/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
  205. package/src/cli/templates/provider/AGENTS.md.tpl +96 -0
  206. package/src/cli/templates/provider/README.md.tpl +7 -4
  207. package/src/config/loader.ts +543 -208
  208. package/src/contract-serialization.ts +5 -11
  209. package/src/contract-types.ts +2 -2
  210. package/src/contract.ts +13 -28
  211. package/src/define.ts +397 -528
  212. package/src/dev.ts +4 -9
  213. package/src/errors.ts +58 -5
  214. package/src/i18n/catalog.ts +10 -32
  215. package/src/i18n/index.ts +2 -2
  216. package/src/i18n/keys.ts +5 -11
  217. package/src/index.ts +64 -41
  218. package/src/lint.ts +122 -159
  219. package/src/provider.ts +40 -9
  220. package/src/public-schema-field-lint.ts +7 -33
  221. package/src/recipes/gov-api.ts +2 -5
  222. package/src/runtime/auth-flow.ts +6 -6
  223. package/src/runtime/browser.ts +320 -151
  224. package/src/runtime/cache.ts +20 -67
  225. package/src/runtime/choice.ts +79 -132
  226. package/src/runtime/credential.ts +2 -2
  227. package/src/runtime/env.ts +1 -1
  228. package/src/runtime/executor.ts +23 -20
  229. package/src/runtime/http.ts +216 -539
  230. package/src/runtime/insights.ts +15 -53
  231. package/src/runtime/instrumentation.ts +78 -65
  232. package/src/runtime/keyring.ts +7 -19
  233. package/src/runtime/namespace.ts +2 -7
  234. package/src/runtime/otlp.ts +12 -23
  235. package/src/runtime/perf.ts +1 -1
  236. package/src/runtime/provider.ts +4 -9
  237. package/src/runtime/proxy-errors.ts +29 -42
  238. package/src/runtime/proxy-nodemaven.ts +178 -0
  239. package/src/runtime/proxy-retry-policy.ts +469 -0
  240. package/src/runtime/proxy-telemetry.ts +79 -77
  241. package/src/runtime/redis.ts +4 -12
  242. package/src/runtime/request-options.ts +4 -13
  243. package/src/runtime/secrets.ts +64 -0
  244. package/src/runtime/state.ts +41 -110
  245. package/src/runtime/stealth.ts +331 -369
  246. package/src/runtime/stt.ts +38 -94
  247. package/src/runtime/trace.ts +14 -44
  248. package/src/runtime/waterfall.ts +5 -18
  249. package/src/schema.ts +23 -84
  250. package/src/serve.ts +1 -1
  251. package/src/server/index.ts +44 -3
  252. package/src/server/self-test-input-tokens.ts +46 -0
  253. package/src/server/self-test-redaction.ts +97 -0
  254. package/src/server/self-test-token.ts +70 -0
  255. package/src/server/self-test.ts +1450 -0
  256. package/src/server/serve.ts +206 -216
  257. package/src/server/types.ts +7 -19
  258. package/src/stealth/profiles.ts +10 -26
  259. package/src/stream.ts +8 -19
  260. package/src/testing/index.ts +2 -2
  261. package/src/testing/run.ts +24 -64
  262. package/src/types.ts +274 -1
  263. package/src/user-input.ts +118 -0
package/dist/auth.js ADDED
@@ -0,0 +1,427 @@
1
+ import { AuthError, ProviderError } from "./errors.js";
2
+ const CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY = "__credentialsAuthChallenge";
3
+ const DEFAULT_COMPLETE_TURN_ID = "auth.complete";
4
+ const DEFAULT_ABORT_TURN_ID = "auth.abort";
5
+ const DEFAULT_FORM_TURN_ID = "auth.form";
6
+ const DEFAULT_POLL_TURN_ID = "auth.poll";
7
+ const SENSITIVE_ABORT_DATA_KEY_PATTERN = /(authorization|cookie|credential|header|html|password|secret|session|token|apikey)/i;
8
+ function normalizedAuthDataKey(key) {
9
+ return key.replace(/[^a-z0-9]/gi, "").toLowerCase();
10
+ }
11
+ function isPlainAuthJsonObject(value) {
12
+ const prototype = Object.getPrototypeOf(value);
13
+ return prototype === Object.prototype || prototype === null;
14
+ }
15
+ function isSensitiveAuthDataKey(key) {
16
+ return SENSITIVE_ABORT_DATA_KEY_PATTERN.test(normalizedAuthDataKey(key));
17
+ }
18
+ function assertSafeAuthJson(value, path) {
19
+ if (value === null)
20
+ return;
21
+ if (typeof value === "string" || typeof value === "boolean")
22
+ return;
23
+ if (typeof value === "number") {
24
+ if (Number.isFinite(value))
25
+ return;
26
+ throw new AuthError(`Auth abort ${path} must be a finite number`, {
27
+ code: "auth_abort_unsafe_data",
28
+ });
29
+ }
30
+ if (Array.isArray(value)) {
31
+ for (const [index, item] of value.entries()) {
32
+ assertSafeAuthJson(item, `${path}[${index}]`);
33
+ }
34
+ return;
35
+ }
36
+ if (value instanceof Error) {
37
+ throw new AuthError(`Auth abort ${path} must not include Error objects`, {
38
+ code: "auth_abort_unsafe_data",
39
+ });
40
+ }
41
+ if (typeof value !== "object") {
42
+ throw new AuthError(`Auth abort ${path} must be JSON-safe`, {
43
+ code: "auth_abort_unsafe_data",
44
+ });
45
+ }
46
+ if (!isPlainAuthJsonObject(value)) {
47
+ throw new AuthError(`Auth abort ${path} must be a plain JSON object`, {
48
+ code: "auth_abort_unsafe_data",
49
+ });
50
+ }
51
+ for (const [key, item] of Object.entries(value)) {
52
+ if (isSensitiveAuthDataKey(key)) {
53
+ throw new AuthError(`Auth abort ${path}.${key} must not include secrets`, {
54
+ code: "auth_abort_unsafe_data",
55
+ });
56
+ }
57
+ assertSafeAuthJson(item, `${path}.${key}`);
58
+ }
59
+ }
60
+ function assertSafeAuthData(data, path) {
61
+ assertSafeAuthJson(data, path);
62
+ }
63
+ function authTurnBase(options) {
64
+ return {
65
+ turnId: options.turnId ?? options.defaultTurnId,
66
+ ...(options.expiresAt ? { expiresAt: options.expiresAt } : {}),
67
+ };
68
+ }
69
+ function abortData(options) {
70
+ if (options.actionHint !== undefined) {
71
+ assertSafeAuthJson(options.actionHint, "actionHint");
72
+ }
73
+ if (options.data !== undefined) {
74
+ assertSafeAuthData(options.data, "data");
75
+ }
76
+ return {
77
+ code: options.code,
78
+ ...(options.message ? { message: options.message } : {}),
79
+ ...(options.retry ? { retry: options.retry } : {}),
80
+ ...(options.actionHint !== undefined ? { actionHint: options.actionHint } : {}),
81
+ ...(options.fieldErrors ? { fieldErrors: options.fieldErrors } : {}),
82
+ ...(options.data ? { details: options.data } : {}),
83
+ };
84
+ }
85
+ export function createAuthFlowHelpers(options = {}) {
86
+ return {
87
+ ...(options.signal ? { signal: options.signal } : {}),
88
+ ...(options.deadline ? { deadline: options.deadline } : {}),
89
+ complete({ credential, metadata, data, turnId, expiresAt }) {
90
+ return {
91
+ kind: "complete",
92
+ ...authTurnBase({ turnId, defaultTurnId: DEFAULT_COMPLETE_TURN_ID, expiresAt }),
93
+ data: {
94
+ ...(data ?? {}),
95
+ credential,
96
+ ...(metadata ? { metadata } : {}),
97
+ },
98
+ };
99
+ },
100
+ abort({ code, message, retry, actionHint, fieldErrors, data, turnId, expiresAt }) {
101
+ return {
102
+ kind: "abort",
103
+ ...authTurnBase({ turnId, defaultTurnId: DEFAULT_ABORT_TURN_ID, expiresAt }),
104
+ data: abortData({
105
+ code,
106
+ message,
107
+ retry,
108
+ actionHint,
109
+ fieldErrors,
110
+ data,
111
+ }),
112
+ };
113
+ },
114
+ nextForm(options) {
115
+ return {
116
+ kind: "form",
117
+ ...authTurnBase({
118
+ turnId: options.turnId,
119
+ defaultTurnId: DEFAULT_FORM_TURN_ID,
120
+ expiresAt: options.expiresAt,
121
+ }),
122
+ ...(options.hintKey ? { hintKey: options.hintKey } : {}),
123
+ ...(options.timing ? { timing: options.timing } : {}),
124
+ ...(options.data ? { data: options.data } : {}),
125
+ expectedInput: options.expectedInput ?? expectedInputFromFields(options.fields ?? {}),
126
+ };
127
+ },
128
+ nextPoll(options = {}) {
129
+ return {
130
+ kind: "poll",
131
+ ...authTurnBase({
132
+ turnId: options.turnId,
133
+ defaultTurnId: DEFAULT_POLL_TURN_ID,
134
+ expiresAt: options.expiresAt,
135
+ }),
136
+ ...(options.hintKey ? { hintKey: options.hintKey } : {}),
137
+ ...(options.timing ? { timing: options.timing } : {}),
138
+ ...(options.data ? { data: options.data } : {}),
139
+ };
140
+ },
141
+ };
142
+ }
143
+ export class AuthAbortError extends AuthError {
144
+ turn;
145
+ constructor(options) {
146
+ super(options.message ?? options.code, {
147
+ code: options.code,
148
+ retryable: options.retry === "retry",
149
+ });
150
+ this.name = "AuthAbortError";
151
+ this.turn = createAuthFlowHelpers().abort(options);
152
+ }
153
+ }
154
+ export function credentialsAuthChallenge(challengeId, options = {}) {
155
+ return {
156
+ kind: "challenge",
157
+ challengeId,
158
+ ...options,
159
+ };
160
+ }
161
+ function expectedInputFromFields(fields) {
162
+ return {
163
+ type: "object",
164
+ properties: Object.fromEntries(Object.entries(fields).map(([name, field]) => [
165
+ name,
166
+ {
167
+ type: "string",
168
+ ...(field.type === "email" ? { format: "email" } : {}),
169
+ ...(field.type === "password" ? { format: "password" } : {}),
170
+ ...(field.type === "otp" ? { format: "otp" } : {}),
171
+ ...(field.labelKey ? { nameKey: field.labelKey } : {}),
172
+ ...(field.descriptionKey ? { descriptionKey: field.descriptionKey } : {}),
173
+ ...(field.placeholderKey ? { placeholderKey: field.placeholderKey } : {}),
174
+ ...(field.sensitive || field.type === "password" || field.type === "otp"
175
+ ? { sensitive: true }
176
+ : {}),
177
+ },
178
+ ])),
179
+ required: Object.entries(fields)
180
+ .filter(([, field]) => field.required !== false)
181
+ .map(([name]) => name),
182
+ };
183
+ }
184
+ function collectMissingFields(fields, input) {
185
+ return Object.entries(fields)
186
+ .filter(([, field]) => field.required !== false)
187
+ .map(([name]) => name)
188
+ .filter((name) => {
189
+ const value = input?.[name];
190
+ return typeof value !== "string" || value.trim().length === 0;
191
+ });
192
+ }
193
+ function normalizeInput(fields, input) {
194
+ const result = {};
195
+ for (const name of Object.keys(fields)) {
196
+ const value = input?.[name];
197
+ result[name] = typeof value === "string" ? value : "";
198
+ }
199
+ return result;
200
+ }
201
+ function assertCredentialKeys(credentialKeys, credential) {
202
+ const missing = credentialKeys.filter((key) => {
203
+ const value = credential[key];
204
+ return typeof value !== "string" || value.length === 0;
205
+ });
206
+ if (missing.length > 0) {
207
+ throw new ProviderError(`Credentials auth login completed without required credential key(s): ${missing.join(", ")}`, {
208
+ code: "credentials_auth_missing_credential_keys",
209
+ fix: "Return every credentialKeys entry from defineCredentialsAuth({ login }) as result.credential. Gateway persists only auth.flow complete data.credential into the connection.",
210
+ });
211
+ }
212
+ }
213
+ function getPendingChallenge(ctx) {
214
+ const value = ctx.context.get(CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY);
215
+ if (!value || typeof value !== "object" || Array.isArray(value))
216
+ return null;
217
+ const record = value;
218
+ if (typeof record.challengeId !== "string")
219
+ return null;
220
+ const state = record.state && typeof record.state === "object" && !Array.isArray(record.state)
221
+ ? record.state
222
+ : {};
223
+ return {
224
+ challengeId: record.challengeId,
225
+ state,
226
+ ...(typeof record.turnId === "string" ? { turnId: record.turnId } : {}),
227
+ ...(typeof record.hintKey === "string" ? { hintKey: record.hintKey } : {}),
228
+ ...(typeof record.expiresAt === "string" ? { expiresAt: record.expiresAt } : {}),
229
+ ...(record.data && typeof record.data === "object" && !Array.isArray(record.data)
230
+ ? { data: record.data }
231
+ : {}),
232
+ ...(record.timing && typeof record.timing === "object" && !Array.isArray(record.timing)
233
+ ? { timing: record.timing }
234
+ : {}),
235
+ };
236
+ }
237
+ function setPendingChallenge(ctx, challenge) {
238
+ ctx.context.set(CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY, challenge);
239
+ }
240
+ function clearPendingChallenge(ctx) {
241
+ ctx.context.set(CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY, null);
242
+ }
243
+ function pendingToChallengeRequest(pending) {
244
+ return {
245
+ kind: "challenge",
246
+ challengeId: pending.challengeId,
247
+ state: pending.state,
248
+ ...(pending.turnId ? { turnId: pending.turnId } : {}),
249
+ ...(pending.hintKey ? { hintKey: pending.hintKey } : {}),
250
+ ...(pending.expiresAt ? { expiresAt: pending.expiresAt } : {}),
251
+ ...(pending.data ? { data: pending.data } : {}),
252
+ ...(pending.timing ? { timing: pending.timing } : {}),
253
+ };
254
+ }
255
+ function retryTurn(expectedInput, missing, retryTurnId) {
256
+ return {
257
+ kind: "retry",
258
+ turnId: retryTurnId,
259
+ expectedInput,
260
+ data: {
261
+ fieldErrors: Object.fromEntries(missing.map((name) => [name, "Required"])),
262
+ fieldErrorKeys: Object.fromEntries(missing.map((name) => [name, "auth.credentials.fieldRequired"])),
263
+ },
264
+ };
265
+ }
266
+ function isChallengeRequest(result) {
267
+ return "kind" in result && result.kind === "challenge";
268
+ }
269
+ function completeTurn(credentialKeys, result, defaultTurnId) {
270
+ if (!result.credential ||
271
+ typeof result.credential !== "object" ||
272
+ Array.isArray(result.credential)) {
273
+ throw new ProviderError("Credentials auth login completed without a credential object", {
274
+ code: "credentials_auth_missing_credential",
275
+ fix: "Return { credential: { ... } } from defineCredentialsAuth handlers. Gateway persists only auth.flow complete data.credential into the connection.",
276
+ });
277
+ }
278
+ assertCredentialKeys(credentialKeys, result.credential);
279
+ return {
280
+ kind: "complete",
281
+ turnId: result.turnId ?? defaultTurnId,
282
+ ...(result.expiresAt ? { expiresAt: result.expiresAt } : {}),
283
+ data: {
284
+ ...(result.data ?? {}),
285
+ credential: result.credential,
286
+ },
287
+ };
288
+ }
289
+ function challengeTurn(definition, request) {
290
+ const expectedInput = definition.fields ? expectedInputFromFields(definition.fields) : undefined;
291
+ return {
292
+ kind: expectedInput ? "form" : "pending",
293
+ turnId: request.turnId ?? definition.turnId ?? `credentials.${request.challengeId}`,
294
+ ...(request.expiresAt ? { expiresAt: request.expiresAt } : {}),
295
+ ...((request.hintKey ?? definition.hintKey)
296
+ ? { hintKey: request.hintKey ?? definition.hintKey }
297
+ : {}),
298
+ ...((request.timing ?? definition.timing)
299
+ ? { timing: request.timing ?? definition.timing }
300
+ : {}),
301
+ ...(expectedInput ? { expectedInput } : {}),
302
+ data: {
303
+ ...(request.data ?? {}),
304
+ challengeId: request.challengeId,
305
+ },
306
+ };
307
+ }
308
+ async function resolveAuthResult(ctx, credentialKeys, challenges, result, completeTurnId) {
309
+ if (!result || typeof result !== "object") {
310
+ throw new AuthError("Credentials auth login did not return a result", {
311
+ code: "credentials_auth_invalid_login_result",
312
+ fix: "Return { credential: { ... } } or credentialsAuthChallenge(...) from defineCredentialsAuth handlers.",
313
+ });
314
+ }
315
+ if (!isChallengeRequest(result)) {
316
+ clearPendingChallenge(ctx);
317
+ return completeTurn(credentialKeys, result, completeTurnId);
318
+ }
319
+ const definition = challenges[result.challengeId];
320
+ if (!definition) {
321
+ throw new ProviderError(`Credentials auth requested unknown challenge "${result.challengeId}"`, {
322
+ code: "credentials_auth_unknown_challenge",
323
+ fix: `Add challenges.${result.challengeId} to defineCredentialsAuth({ challenges }).`,
324
+ });
325
+ }
326
+ setPendingChallenge(ctx, {
327
+ challengeId: result.challengeId,
328
+ state: result.state ?? {},
329
+ ...(result.turnId ? { turnId: result.turnId } : {}),
330
+ ...(result.hintKey ? { hintKey: result.hintKey } : {}),
331
+ ...(result.expiresAt ? { expiresAt: result.expiresAt } : {}),
332
+ ...(result.data ? { data: result.data } : {}),
333
+ ...(result.timing ? { timing: result.timing } : {}),
334
+ });
335
+ return challengeTurn(definition, result);
336
+ }
337
+ async function continuePendingChallenge(ctx, credentialKeys, challenges, pending, rawInput, completeTurnId) {
338
+ const definition = challenges[pending.challengeId];
339
+ if (!definition) {
340
+ throw new ProviderError(`Credentials auth has pending unknown challenge "${pending.challengeId}"`, { code: "credentials_auth_unknown_pending_challenge" });
341
+ }
342
+ if (!definition.fields || !definition.verify) {
343
+ return challengeTurn(definition, pendingToChallengeRequest(pending));
344
+ }
345
+ const missing = collectMissingFields(definition.fields, rawInput);
346
+ const expectedInput = expectedInputFromFields(definition.fields);
347
+ if (missing.length > 0) {
348
+ return retryTurn(expectedInput, missing, definition.retryTurnId ?? `credentials.${pending.challengeId}.retry`);
349
+ }
350
+ const result = await definition.verify(ctx, normalizeInput(definition.fields, rawInput), pending.state);
351
+ return await resolveAuthResult(ctx, credentialKeys, challenges, result, completeTurnId);
352
+ }
353
+ async function pollPendingChallenge(ctx, credentialKeys, challenges, pending, completeTurnId) {
354
+ const definition = challenges[pending.challengeId];
355
+ if (!definition) {
356
+ throw new ProviderError(`Credentials auth has pending unknown challenge "${pending.challengeId}"`, { code: "credentials_auth_unknown_pending_challenge" });
357
+ }
358
+ if (!definition.poll) {
359
+ return challengeTurn(definition, pendingToChallengeRequest(pending));
360
+ }
361
+ const result = await definition.poll(ctx, pending.state);
362
+ if (!result) {
363
+ return {
364
+ ...challengeTurn(definition, pendingToChallengeRequest(pending)),
365
+ turnId: definition.pendingTurnId ??
366
+ definition.turnId ??
367
+ `credentials.${pending.challengeId}.pending`,
368
+ };
369
+ }
370
+ return await resolveAuthResult(ctx, credentialKeys, challenges, result, completeTurnId);
371
+ }
372
+ export function defineCredentialsAuth(options) {
373
+ if (Object.keys(options.fields).length === 0) {
374
+ throw new ProviderError("defineCredentialsAuth requires at least one field", {
375
+ fix: 'Pass fields such as { email: { type: "email" }, password: { type: "password" } }.',
376
+ });
377
+ }
378
+ const expectedInput = expectedInputFromFields(options.fields);
379
+ const retryTurnId = options.retryTurnId ?? "credentials.retry";
380
+ const completeTurnId = options.completeTurnId ?? "credentials.complete";
381
+ const challenges = (options.challenges ?? {});
382
+ return {
383
+ auth: {
384
+ mode: "credentials",
385
+ flow: {
386
+ start: async () => ({
387
+ kind: "form",
388
+ turnId: options.startTurnId ?? "credentials.start",
389
+ ...(options.hintKey ? { hintKey: options.hintKey } : {}),
390
+ expectedInput,
391
+ }),
392
+ continue: async (ctx, rawInput) => {
393
+ const pending = getPendingChallenge(ctx);
394
+ if (pending) {
395
+ return await continuePendingChallenge(ctx, options.credentialKeys, challenges, pending, rawInput, completeTurnId);
396
+ }
397
+ const missing = collectMissingFields(options.fields, rawInput);
398
+ if (missing.length > 0) {
399
+ return retryTurn(expectedInput, missing, retryTurnId);
400
+ }
401
+ const result = await options.login(ctx, normalizeInput(options.fields, rawInput));
402
+ return await resolveAuthResult(ctx, options.credentialKeys, challenges, result, completeTurnId);
403
+ },
404
+ poll: async (ctx) => {
405
+ const pending = getPendingChallenge(ctx);
406
+ if (!pending) {
407
+ return {
408
+ kind: "pending",
409
+ turnId: "credentials.noPendingChallenge",
410
+ };
411
+ }
412
+ return await pollPendingChallenge(ctx, options.credentialKeys, challenges, pending, completeTurnId);
413
+ },
414
+ },
415
+ },
416
+ credential: {
417
+ keys: Array.from(options.credentialKeys),
418
+ ...(options.storesReusableSecret === undefined
419
+ ? {}
420
+ : { storesReusableSecret: options.storesReusableSecret }),
421
+ ...(options.justification ? { justification: options.justification } : {}),
422
+ },
423
+ context: {
424
+ keys: Array.from(new Set([CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY, ...(options.contextKeys ?? [])])),
425
+ },
426
+ };
427
+ }
@@ -1,4 +1,4 @@
1
- import type { AuthFlowDefinition, AuthTurn } from "../types";
1
+ import type { AuthFlowDefinition, AuthTurn } from "../types.js";
2
2
  type JsonObject = Record<string, unknown>;
3
3
  export declare function validateCeremonyOutput(turn: unknown): AuthTurn;
4
4
  export declare function createOAuth2Ceremony(options: {
@@ -1,36 +1,12 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
- import Ajv from "ajv";
3
- import { FlowExpiredError, ProviderSecretError, TurnValidationError, ValidationError, } from "../errors";
4
- const ajv = new Ajv({ allErrors: true, strict: true, strictSchema: true });
5
- const authTurnSchema = {
6
- type: "object",
7
- additionalProperties: false,
8
- required: ["kind", "turnId"],
9
- properties: {
10
- kind: { type: "string", minLength: 1 },
11
- turnId: { type: "string", minLength: 1 },
12
- expiresAt: { type: "string", minLength: 1 },
13
- data: {
14
- type: "object",
15
- additionalProperties: true,
16
- },
17
- expectedInput: {
18
- type: "object",
19
- additionalProperties: true,
20
- },
21
- hint: { type: "string" },
22
- hintKey: { type: "string" },
23
- timing: {
24
- type: "object",
25
- additionalProperties: false,
26
- properties: {
27
- suggestedPollIntervalMs: { type: "number", minimum: 1 },
28
- maxWaitMs: { type: "number", minimum: 1 },
29
- },
30
- },
31
- },
32
- };
33
- const validateAuthTurn = ajv.compile(authTurnSchema);
2
+ import Ajv2020 from "ajv/dist/2020.js";
3
+ import { AUTH_TURN_SCHEMA } from "../auth-turn/index.js";
4
+ import { FlowExpiredError, ProviderSecretError, TurnValidationError, ValidationError, } from "../errors.js";
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);
34
10
  const OAUTH2_STATE_KEY = "__oauth2_state";
35
11
  const OAUTH2_PKCE_VERIFIER_KEY = "__oauth2_pkce_verifier";
36
12
  const DEVICE_FLOW_KEY = "__device_flow";
@@ -62,11 +38,7 @@ function getRequiredEnv(ctx, key) {
62
38
  return value;
63
39
  }
64
40
  function toBase64Url(input) {
65
- return input
66
- .toString("base64")
67
- .replace(/\+/g, "-")
68
- .replace(/\//g, "_")
69
- .replace(/=+$/g, "");
41
+ return input.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
70
42
  }
71
43
  function createCodeVerifier() {
72
44
  return toBase64Url(randomBytes(32));
@@ -124,8 +96,7 @@ function withDeclaredFormFieldOrder(expectedInput) {
124
96
  return expectedInput;
125
97
  }
126
98
  const existingOrder = expectedInput[FORM_FIELD_ORDER_EXTENSION];
127
- if (Array.isArray(existingOrder) &&
128
- existingOrder.every((value) => typeof value === "string")) {
99
+ if (Array.isArray(existingOrder) && existingOrder.every((value) => typeof value === "string")) {
129
100
  return expectedInput;
130
101
  }
131
102
  return {
@@ -242,8 +213,7 @@ export function createDeviceFlowCeremony(options) {
242
213
  });
243
214
  const data = ensureRecord(response.data);
244
215
  const errorCode = getString(data, "error");
245
- if (errorCode === "authorization_pending" ||
246
- errorCode === "slow_down") {
216
+ if (errorCode === "authorization_pending" || errorCode === "slow_down") {
247
217
  return createTurn("poll", {
248
218
  data,
249
219
  hint: "Authorization pending.",
@@ -369,7 +339,7 @@ export function createFormCeremony(options) {
369
339
  return {
370
340
  start: async () => validateCeremonyOutput(buildJsonSchemaForm(options.schema, options.hint ?? "Provide the required input to continue.")),
371
341
  continue: (ctx, input = {}) => runCeremonyHandler(async () => {
372
- const { prevalidate } = await import("../runtime/prevalidate");
342
+ const { prevalidate } = await import("../runtime/prevalidate.js");
373
343
  const result = prevalidate(options.schema, input);
374
344
  if (!result.valid) {
375
345
  throw new ValidationError("Form input failed validation.", {
@@ -378,9 +348,7 @@ export function createFormCeremony(options) {
378
348
  }
379
349
  return createTurn("complete", {
380
350
  data: {
381
- credential: options.mapCredential
382
- ? options.mapCredential(input)
383
- : input,
351
+ credential: options.mapCredential ? options.mapCredential(input) : input,
384
352
  },
385
353
  hint: "Form completed.",
386
354
  });
@@ -452,9 +420,7 @@ export function createSwitchCeremony(options) {
452
420
  })),
453
421
  continue: (ctx, input = {}) => runCeremonyHandler(async () => {
454
422
  const storedChoice = ctx.context.get(SWITCH_SELECTION_KEY);
455
- const choice = typeof storedChoice === "string"
456
- ? storedChoice
457
- : getString(input, "choice");
423
+ const choice = typeof storedChoice === "string" ? storedChoice : getString(input, "choice");
458
424
  if (!choice || !options.choices[choice]) {
459
425
  throw new ValidationError("A valid choice is required.");
460
426
  }
@@ -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;
@@ -23,6 +23,13 @@ export const COMMAND_MANIFEST = {
23
23
  examples: ["apifuse check .", "apifuse check providers/korea-air-quality"],
24
24
  modulePath: "./apifuse-check",
25
25
  },
26
+ "sync-assets": {
27
+ name: "sync-assets",
28
+ summary: "Regenerate SDK-managed agent prompt assets (AGENTS.md, .agents/skills, symlinks, manifest) for the installed SDK version.",
29
+ usage: "apifuse sync-assets [path] [--check]",
30
+ examples: ["apifuse sync-assets .", "apifuse sync-assets . --check"],
31
+ modulePath: "./apifuse-sync-assets",
32
+ },
26
33
  "submit-check": {
27
34
  name: "submit-check",
28
35
  summary: "Score provider bounty submission readiness and emit checklist evidence.",
@@ -73,6 +80,7 @@ export const COMMAND_ORDER = [
73
80
  "create",
74
81
  "dev",
75
82
  "check",
83
+ "sync-assets",
76
84
  "submit-check",
77
85
  "record",
78
86
  "test",
@@ -26,7 +26,10 @@ export type CreateResolvedOptions = {
26
26
  };
27
27
  export type ProviderPlanFile = {
28
28
  path: string;
29
+ /** File content, or the symlink target (relative path) for kind "symlink". */
29
30
  content: string;
31
+ /** Absent kind means "file" (backward compatible with older consumers). */
32
+ kind?: "file" | "symlink";
30
33
  };
31
34
  export type ProviderCreatePlan = {
32
35
  displayName: string;