@apifuse/provider-sdk 2.2.0-beta.1 → 2.2.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/AUTHORING.md +106 -0
  2. package/CHANGELOG.md +37 -0
  3. package/README.md +5 -1
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +14 -0
  9. package/bin/apifuse-pack-smoke.ts +22 -81
  10. package/bin/apifuse-pack-types.ts +266 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +53 -70
  13. package/bin/apifuse-submit-check.ts +510 -45
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth.d.ts +2 -2
  21. package/dist/auth.js +9 -18
  22. package/dist/ceremonies/index.d.ts +1 -1
  23. package/dist/ceremonies/index.js +8 -18
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/config/loader.d.ts +149 -8
  32. package/dist/config/loader.js +378 -89
  33. package/dist/contract-serialization.d.ts +2 -2
  34. package/dist/contract-serialization.js +3 -6
  35. package/dist/contract-types.d.ts +2 -2
  36. package/dist/contract.d.ts +3 -3
  37. package/dist/contract.js +4 -6
  38. package/dist/define.d.ts +8 -1
  39. package/dist/define.js +119 -122
  40. package/dist/dev.d.ts +1 -1
  41. package/dist/dev.js +1 -1
  42. package/dist/errors.d.ts +4 -1
  43. package/dist/errors.js +48 -0
  44. package/dist/i18n/catalog.d.ts +2 -2
  45. package/dist/i18n/catalog.js +4 -10
  46. package/dist/i18n/index.d.ts +2 -2
  47. package/dist/i18n/index.js +2 -2
  48. package/dist/i18n/keys.d.ts +2 -2
  49. package/dist/index.d.ts +44 -42
  50. package/dist/index.js +39 -37
  51. package/dist/lint.js +8 -15
  52. package/dist/provider.d.ts +11 -11
  53. package/dist/provider.js +9 -9
  54. package/dist/public-schema-field-lint.d.ts +1 -1
  55. package/dist/recipes/gov-api.js +1 -1
  56. package/dist/runtime/auth-flow.d.ts +1 -1
  57. package/dist/runtime/auth-flow.js +3 -3
  58. package/dist/runtime/browser.d.ts +1 -1
  59. package/dist/runtime/browser.js +15 -29
  60. package/dist/runtime/cache.d.ts +1 -1
  61. package/dist/runtime/cache.js +4 -8
  62. package/dist/runtime/choice.d.ts +1 -1
  63. package/dist/runtime/choice.js +31 -35
  64. package/dist/runtime/credential.d.ts +1 -1
  65. package/dist/runtime/credential.js +1 -1
  66. package/dist/runtime/env.d.ts +1 -1
  67. package/dist/runtime/executor.d.ts +1 -1
  68. package/dist/runtime/executor.js +15 -3
  69. package/dist/runtime/http.d.ts +2 -2
  70. package/dist/runtime/http.js +144 -348
  71. package/dist/runtime/insights.d.ts +1 -1
  72. package/dist/runtime/insights.js +6 -13
  73. package/dist/runtime/instrumentation.d.ts +2 -2
  74. package/dist/runtime/instrumentation.js +56 -19
  75. package/dist/runtime/keyring.js +1 -1
  76. package/dist/runtime/namespace.js +1 -1
  77. package/dist/runtime/otlp.d.ts +1 -1
  78. package/dist/runtime/perf.d.ts +1 -1
  79. package/dist/runtime/provider.d.ts +1 -1
  80. package/dist/runtime/provider.js +1 -2
  81. package/dist/runtime/proxy-errors.d.ts +1 -1
  82. package/dist/runtime/proxy-errors.js +9 -7
  83. package/dist/runtime/proxy-nodemaven.d.ts +35 -0
  84. package/dist/runtime/proxy-nodemaven.js +128 -0
  85. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  86. package/dist/runtime/proxy-retry-policy.js +326 -0
  87. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  88. package/dist/runtime/proxy-telemetry.js +55 -52
  89. package/dist/runtime/redis.d.ts +1 -1
  90. package/dist/runtime/redis.js +2 -4
  91. package/dist/runtime/request-options.d.ts +1 -1
  92. package/dist/runtime/secrets.d.ts +27 -0
  93. package/dist/runtime/secrets.js +51 -0
  94. package/dist/runtime/state.d.ts +2 -2
  95. package/dist/runtime/state.js +15 -4
  96. package/dist/runtime/stealth.d.ts +2 -2
  97. package/dist/runtime/stealth.js +108 -212
  98. package/dist/runtime/stt.d.ts +1 -1
  99. package/dist/runtime/stt.js +11 -15
  100. package/dist/runtime/trace.d.ts +2 -2
  101. package/dist/runtime/trace.js +2 -4
  102. package/dist/runtime/waterfall.d.ts +1 -1
  103. package/dist/schema.d.ts +1 -1
  104. package/dist/schema.js +7 -15
  105. package/dist/serve.d.ts +1 -1
  106. package/dist/serve.js +1 -1
  107. package/dist/server/index.d.ts +7 -7
  108. package/dist/server/index.js +6 -6
  109. package/dist/server/self-test-redaction.d.ts +1 -1
  110. package/dist/server/self-test-redaction.js +1 -1
  111. package/dist/server/self-test.d.ts +104 -3
  112. package/dist/server/self-test.js +673 -115
  113. package/dist/server/serve.d.ts +8 -3
  114. package/dist/server/serve.js +105 -59
  115. package/dist/server/types.d.ts +10 -9
  116. package/dist/server/types.js +3 -7
  117. package/dist/stealth/profiles.d.ts +1 -1
  118. package/dist/stealth/profiles.js +5 -14
  119. package/dist/stream.d.ts +1 -1
  120. package/dist/testing/index.d.ts +2 -2
  121. package/dist/testing/index.js +2 -2
  122. package/dist/testing/run.d.ts +1 -1
  123. package/dist/testing/run.js +7 -14
  124. package/dist/types.d.ts +87 -1
  125. package/dist/user-input.d.ts +30 -0
  126. package/dist/user-input.js +66 -0
  127. package/package.json +9 -5
  128. package/src/auth-turn/index.ts +1 -1
  129. package/src/auth.ts +28 -86
  130. package/src/ceremonies/index.ts +24 -78
  131. package/src/cli/commands.ts +10 -0
  132. package/src/cli/create.ts +42 -35
  133. package/src/cli/prompt-assets.ts +865 -0
  134. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  135. package/src/config/loader.ts +543 -208
  136. package/src/contract-serialization.ts +5 -11
  137. package/src/contract-types.ts +2 -2
  138. package/src/contract.ts +12 -28
  139. package/src/define.ts +247 -505
  140. package/src/dev.ts +4 -9
  141. package/src/errors.ts +58 -5
  142. package/src/i18n/catalog.ts +10 -32
  143. package/src/i18n/index.ts +2 -2
  144. package/src/i18n/keys.ts +5 -11
  145. package/src/index.ts +49 -42
  146. package/src/lint.ts +89 -159
  147. package/src/provider.ts +15 -11
  148. package/src/public-schema-field-lint.ts +7 -33
  149. package/src/recipes/gov-api.ts +2 -5
  150. package/src/runtime/auth-flow.ts +5 -7
  151. package/src/runtime/browser.ts +64 -187
  152. package/src/runtime/cache.ts +20 -67
  153. package/src/runtime/choice.ts +79 -132
  154. package/src/runtime/credential.ts +2 -2
  155. package/src/runtime/env.ts +1 -1
  156. package/src/runtime/executor.ts +23 -20
  157. package/src/runtime/http.ts +195 -559
  158. package/src/runtime/insights.ts +15 -53
  159. package/src/runtime/instrumentation.ts +78 -65
  160. package/src/runtime/keyring.ts +7 -19
  161. package/src/runtime/namespace.ts +2 -7
  162. package/src/runtime/otlp.ts +12 -23
  163. package/src/runtime/perf.ts +1 -1
  164. package/src/runtime/provider.ts +4 -9
  165. package/src/runtime/proxy-errors.ts +29 -42
  166. package/src/runtime/proxy-nodemaven.ts +178 -0
  167. package/src/runtime/proxy-retry-policy.ts +469 -0
  168. package/src/runtime/proxy-telemetry.ts +79 -77
  169. package/src/runtime/redis.ts +4 -12
  170. package/src/runtime/request-options.ts +4 -13
  171. package/src/runtime/secrets.ts +64 -0
  172. package/src/runtime/state.ts +41 -110
  173. package/src/runtime/stealth.ts +162 -377
  174. package/src/runtime/stt.ts +38 -94
  175. package/src/runtime/trace.ts +14 -44
  176. package/src/runtime/waterfall.ts +5 -18
  177. package/src/schema.ts +23 -84
  178. package/src/serve.ts +1 -1
  179. package/src/server/index.ts +12 -7
  180. package/src/server/self-test-redaction.ts +2 -2
  181. package/src/server/self-test.ts +857 -132
  182. package/src/server/serve.ts +172 -245
  183. package/src/server/types.ts +7 -19
  184. package/src/stealth/profiles.ts +10 -26
  185. package/src/stream.ts +8 -19
  186. package/src/testing/index.ts +2 -2
  187. package/src/testing/run.ts +19 -67
  188. package/src/types.ts +88 -1
  189. package/src/user-input.ts +118 -0
  190. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  191. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  192. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  193. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  194. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  195. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  196. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  197. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  198. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  199. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  200. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  201. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  202. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  203. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -3,62 +3,65 @@ import { join } from "node:path";
3
3
 
4
4
  import { Hono } from "hono";
5
5
  import { z } from "zod";
6
- import { AuthAbortError, createAuthFlowHelpers } from "../auth";
6
+ import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
7
7
  import {
8
8
  AuthError,
9
+ isProviderError,
10
+ isSessionExpiredError,
11
+ isTransportError,
9
12
  ProviderError,
10
- SessionExpiredError,
11
- TransportError,
12
- } from "../errors";
13
+ } from "../errors.js";
13
14
  import {
14
15
  loadProviderLocaleCatalogs,
15
16
  localizeAuthTurn,
16
17
  type ProviderLocaleCatalogMap,
17
- } from "../i18n/catalog";
18
- import type { ProviderLocale } from "../i18n/keys";
18
+ } from "../i18n/catalog.js";
19
+ import type { ProviderLocale } from "../i18n/keys.js";
19
20
  import {
20
21
  categoryForStatus,
21
22
  isRetryableCategory,
22
23
  PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
23
24
  type ProviderErrorCategory,
24
- } from "../observability";
25
- import { createScratchpad } from "../runtime/auth-flow";
26
- import { createBrowserClient } from "../runtime/browser";
27
- import { createProviderCache } from "../runtime/cache";
25
+ } from "../observability.js";
26
+ import { createScratchpad } from "../runtime/auth-flow.js";
27
+ import { createBrowserClient } from "../runtime/browser.js";
28
+ import { createProviderCache } from "../runtime/cache.js";
28
29
  import {
29
30
  createProviderChoiceContext,
30
31
  PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
31
- } from "../runtime/choice";
32
- import { createCredentialContext } from "../runtime/credential";
33
- import { createEnvContext } from "../runtime/env";
34
- import { executeOperation } from "../runtime/executor";
35
- import { createHttpClient } from "../runtime/http";
36
- import { wrapWithInstrumentation } from "../runtime/instrumentation";
37
- import { getProviderBaseUrl } from "../runtime/provider";
32
+ } from "../runtime/choice.js";
33
+ import { createCredentialContext } from "../runtime/credential.js";
34
+ import { createEnvContext } from "../runtime/env.js";
35
+ import { executeOperation } from "../runtime/executor.js";
36
+ import { createHttpClient } from "../runtime/http.js";
37
+ import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
38
+ import { getProviderBaseUrl } from "../runtime/provider.js";
38
39
  import {
39
40
  PROXY_AUTH_IP_DENIED_CODE,
40
41
  PROXY_EDGE_AUTH_REJECTED_CODE,
41
42
  PROXY_POOL_EXHAUSTED_CODE,
42
- } from "../runtime/proxy-errors";
43
+ } from "../runtime/proxy-errors.js";
44
+ import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector } from "../runtime/proxy-telemetry.js";
43
45
  import {
44
- PROVIDER_TELEMETRY_HEADER,
45
- ProxyTelemetryCollector,
46
- } from "../runtime/proxy-telemetry";
46
+ assertRequiredSecretsPresent,
47
+ listMissingRequiredSecrets,
48
+ MISSING_SECRET_CODE,
49
+ } from "../runtime/secrets.js";
47
50
  import {
48
51
  createProviderRuntimeStateFromEnv,
49
52
  createUnsupportedProviderRuntimeState,
50
- } from "../runtime/state";
51
- import { createStealthClient } from "../runtime/stealth";
52
- import { createSttClientFromEnv } from "../runtime/stt";
53
- import { createTraceContext } from "../runtime/trace";
54
- import { parseSchema } from "../schema";
55
- import { getStealthProfile } from "../stealth/profiles";
53
+ } from "../runtime/state.js";
54
+ import { createStealthClient } from "../runtime/stealth.js";
55
+ import { createSttClientFromEnv } from "../runtime/stt.js";
56
+ import { createTraceContext } from "../runtime/trace.js";
57
+ import { parseSchema } from "../schema.js";
58
+ import { getStealthProfile } from "../stealth/profiles.js";
56
59
  import {
57
60
  APIFUSE_STREAM_DONE_EVENT,
58
61
  APIFUSE_STREAM_ERROR_EVENT,
59
62
  encodeSseEvent,
60
63
  error as streamError,
61
- } from "../stream";
64
+ } from "../stream.js";
62
65
  import type {
63
66
  AuthContext,
64
67
  AuthTurn,
@@ -75,13 +78,14 @@ import type {
75
78
  ProviderStreamEvent,
76
79
  StealthClient,
77
80
  SttContext,
78
- } from "../types";
81
+ } from "../types.js";
79
82
  import {
80
83
  createSelfTestApp,
84
+ createSelfTestAuthFlowInvoke,
81
85
  createSelfTestInvoke,
82
86
  resolveSelfTestPort,
83
- } from "./self-test";
84
- import { resolveSelfTestMasterSecrets } from "./self-test-token";
87
+ } from "./self-test.js";
88
+ import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
85
89
  import {
86
90
  type AuthFlowRequest,
87
91
  AuthFlowRequestSchema,
@@ -92,7 +96,7 @@ import {
92
96
  OperationRequestSchema,
93
97
  type OperationResponse,
94
98
  type OperationSuccessResponse,
95
- } from "./types";
99
+ } from "./types.js";
96
100
 
97
101
  const DEFAULT_HOST = "0.0.0.0";
98
102
  const DEFAULT_PORT = 3000;
@@ -156,9 +160,7 @@ function createStealthStub(): StealthClient {
156
160
  };
157
161
  }
158
162
 
159
- function getProviderStealthBaseUrl(
160
- provider: ProviderDefinition,
161
- ): string | undefined {
163
+ function getProviderStealthBaseUrl(provider: ProviderDefinition): string | undefined {
162
164
  const baseUrl = getProviderBaseUrl(provider);
163
165
  if (baseUrl) {
164
166
  return baseUrl;
@@ -168,15 +170,10 @@ function getProviderStealthBaseUrl(
168
170
  }
169
171
 
170
172
  function getProviderStealthProfile(provider: ProviderDefinition) {
171
- return provider.stealth?.profile
172
- ? getStealthProfile(provider.stealth.profile)
173
- : undefined;
173
+ return provider.stealth?.profile ? getStealthProfile(provider.stealth.profile) : undefined;
174
174
  }
175
175
 
176
- function isProductionProviderBrowserMode(
177
- provider: ProviderDefinition,
178
- env = process.env,
179
- ): boolean {
176
+ function isProductionProviderBrowserMode(provider: ProviderDefinition, env = process.env): boolean {
180
177
  if (provider.runtime !== "browser") {
181
178
  return false;
182
179
  }
@@ -185,9 +182,7 @@ function isProductionProviderBrowserMode(
185
182
  return true;
186
183
  }
187
184
 
188
- return (
189
- env.NODE_ENV === "production" && env.APIFUSE__PROVIDER__ID === provider.id
190
- );
185
+ return env.NODE_ENV === "production" && env.APIFUSE__PROVIDER__ID === provider.id;
191
186
  }
192
187
 
193
188
  export function resolveProviderProxyAffinityKey(
@@ -195,18 +190,19 @@ export function resolveProviderProxyAffinityKey(
195
190
  request: OperationRequest,
196
191
  operationId: string,
197
192
  ): string {
198
- const connectionKey =
199
- request.connection?.id ?? request.connection?.externalRef;
193
+ const connectionKey = resolveOperationConnectionId(request) ?? request.connection?.externalRef;
200
194
  const affinity =
201
- typeof provider.proxy === "object"
202
- ? provider.proxy.session?.affinity
203
- : undefined;
195
+ typeof provider.proxy === "object" ? provider.proxy.session?.affinity : undefined;
204
196
  if (affinity === "operation") {
205
197
  return `${provider.id}/${operationId}`;
206
198
  }
207
199
  return connectionKey ?? provider.id;
208
200
  }
209
201
 
202
+ function resolveOperationConnectionId(request: OperationRequest): string | undefined {
203
+ return request.connection?.id ?? request.connectionId;
204
+ }
205
+
210
206
  function createProviderContext(
211
207
  provider: ProviderDefinition,
212
208
  request: OperationRequest,
@@ -220,11 +216,7 @@ function createProviderContext(
220
216
  const stealthProfile = getProviderStealthProfile(provider);
221
217
  const proxyClientOptions = {
222
218
  upstream: { proxy: provider.proxy },
223
- affinityKey: resolveProviderProxyAffinityKey(
224
- provider,
225
- request,
226
- operationId,
227
- ),
219
+ affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
228
220
  telemetry: proxyTelemetry,
229
221
  };
230
222
  let wrappedContext: ProviderContext | undefined;
@@ -245,7 +237,7 @@ function createProviderContext(
245
237
  values: request.connection?.secrets,
246
238
  });
247
239
  const requestContext = {
248
- connectionId: request.connection?.id,
240
+ connectionId: resolveOperationConnectionId(request),
249
241
  headers: request.headers ?? {},
250
242
  };
251
243
  const context = wrapWithInstrumentation({
@@ -263,11 +255,7 @@ function createProviderContext(
263
255
  state,
264
256
  stealth: stealthBaseUrl
265
257
  ? stealthProfile
266
- ? createStealthClient(
267
- stealthBaseUrl,
268
- stealthProfile.name,
269
- stealthClientOptions,
270
- )
258
+ ? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
271
259
  : createStealthClient(stealthBaseUrl, stealthClientOptions)
272
260
  : createStealthStub(),
273
261
  browser:
@@ -380,11 +368,7 @@ function createAuthFlowContext(
380
368
  http: createHttpClient(baseUrl, proxyClientOptions),
381
369
  stealth: stealthBaseUrl
382
370
  ? stealthProfile
383
- ? createStealthClient(
384
- stealthBaseUrl,
385
- stealthProfile.name,
386
- stealthClientOptions,
387
- )
371
+ ? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
388
372
  : createStealthClient(stealthBaseUrl, stealthClientOptions)
389
373
  : createStealthStub(),
390
374
  env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
@@ -429,6 +413,12 @@ export type ProviderServerLogEvent =
429
413
  retryable?: boolean;
430
414
  issues?: Array<{ path: string; code: string; message: string }>;
431
415
  })
416
+ | {
417
+ level: "warn";
418
+ event: "provider_secrets_missing";
419
+ providerId: string;
420
+ missingSecrets: string[];
421
+ }
432
422
  | {
433
423
  level: "warn";
434
424
  event: "provider_cleanup_failed";
@@ -497,11 +487,8 @@ function zodDetails(error: z.ZodError): Array<{
497
487
  }));
498
488
  }
499
489
 
500
- function toErrorResponse(
501
- error: unknown,
502
- requestId?: string,
503
- ): OperationErrorResponse {
504
- if (error instanceof ProviderError) {
490
+ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResponse {
491
+ if (isProviderError(error)) {
505
492
  const details = publicProviderErrorDetails(error);
506
493
  return {
507
494
  error: {
@@ -525,11 +512,22 @@ function toErrorResponse(
525
512
  };
526
513
  }
527
514
 
515
+ // A masked internal error MUST NOT be advertised as retryable: without an
516
+ // explicit retryable:false the hub (bori provider-backed engine) defaults 5xx
517
+ // to retryable:true, which turns a deterministic pre-upstream crash into an
518
+ // infinite START->CONTINUE->restart loop (2026-07-22 catchtable reserve RCA).
519
+ // We still refuse to leak message/stack — only the error class name (or the
520
+ // primitive type for non-Error throwables) is surfaced for ops triage.
528
521
  return {
529
522
  error: {
530
523
  code: "internal_error",
531
524
  message: "Internal error",
532
525
  ...(requestId ? { requestId } : {}),
526
+ details: {
527
+ retryable: false,
528
+ category: "internal_error",
529
+ errorClass: error instanceof Error ? error.name : typeof error,
530
+ },
533
531
  },
534
532
  };
535
533
  }
@@ -557,7 +555,12 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
557
555
  return value !== null && typeof value === "object" && !Array.isArray(value);
558
556
  }
559
557
 
560
- function providerObservabilityDetails(error: ProviderError):
558
+ // Accepts `unknown` so the branded guards narrow cleanly from the top: the
559
+ // subtype error classes are structurally compatible with ProviderError, so
560
+ // narrowing from a ProviderError-typed value would collapse the negative branch
561
+ // to `never`. Narrowing from unknown avoids that while still recognizing errors
562
+ // from a duplicate SDK module instance.
563
+ function providerObservabilityDetails(error: unknown):
561
564
  | {
562
565
  category: ProviderErrorCategory;
563
566
  taxonomyVersion: string;
@@ -570,14 +573,26 @@ function providerObservabilityDetails(error: ProviderError):
570
573
  // operation (see design.md §4.3 D3). Without this branch the auth error would
571
574
  // serialize as a bare 401 with no retryable/category, losing the refresh
572
575
  // signal for exactly the retryOnAuthRefresh operations it is meant to enable.
573
- if (error instanceof SessionExpiredError) {
576
+ if (isSessionExpiredError(error)) {
574
577
  return {
575
578
  category: error.options?.category ?? "credential_expired",
576
579
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
577
580
  retryable: error.options?.retryable ?? false,
578
581
  };
579
582
  }
580
- if (!(error instanceof TransportError)) {
583
+ // Missing-secret errors carry the canonical credential_unavailable category
584
+ // so Gateway/observability can attribute the failure to provisioning, not
585
+ // the upstream. Matched by code (not constructor) so both the SDK-owned
586
+ // runtime gate and any not-yet-migrated provider-thrown MISSING_SECRET
587
+ // serialize identically, including across duplicate SDK module instances.
588
+ if (isProviderError(error) && error.code === MISSING_SECRET_CODE) {
589
+ return {
590
+ category: error.options?.category ?? "credential_unavailable",
591
+ taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
592
+ retryable: error.options?.retryable ?? false,
593
+ };
594
+ }
595
+ if (!isTransportError(error)) {
581
596
  return undefined;
582
597
  }
583
598
  const isProxyPoolCode =
@@ -610,7 +625,7 @@ function providerObservabilityDetails(error: ProviderError):
610
625
  }
611
626
 
612
627
  function publicProviderErrorMessage(error: ProviderError): string {
613
- if (error instanceof TransportError) {
628
+ if (isTransportError(error)) {
614
629
  if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
615
630
  return error.message;
616
631
  }
@@ -633,22 +648,24 @@ function publicProviderErrorMessage(error: ProviderError): string {
633
648
  return error.message;
634
649
  }
635
650
 
636
- function toStatusCode(
637
- error: unknown,
638
- ): 400 | 401 | 404 | 429 | 500 | 502 | 503 | 504 {
651
+ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 | 504 {
639
652
  if (error instanceof z.ZodError) {
640
653
  return 400;
641
654
  }
642
655
 
643
- if (error instanceof TransportError) {
656
+ if (isTransportError(error)) {
644
657
  return error.code === "transport_timeout" ? 504 : 502;
645
658
  }
646
659
 
647
- if (error instanceof ProviderError) {
660
+ if (isProviderError(error)) {
648
661
  switch (error.code) {
649
662
  case "AUTH_REQUIRED":
650
663
  case "reauth_required":
651
664
  return 401;
665
+ // Unprovisioned declared secret: a deployment/config defect, never an
666
+ // upstream failure — explicit 400 (was only reached via fallthrough).
667
+ case MISSING_SECRET_CODE:
668
+ return 400;
652
669
  case "NOT_FOUND":
653
670
  case "not_found":
654
671
  case "NO_DATA":
@@ -690,20 +707,15 @@ function logProviderError(
690
707
  status: number,
691
708
  cost: ProviderRequestCost,
692
709
  ): void {
693
- const code =
694
- error instanceof ProviderError
695
- ? (error.code ?? "provider_error")
696
- : error instanceof z.ZodError
697
- ? "invalid_request"
698
- : "internal_error";
710
+ const code = isProviderError(error)
711
+ ? (error.code ?? "provider_error")
712
+ : error instanceof z.ZodError
713
+ ? "invalid_request"
714
+ : "internal_error";
699
715
  const errorClass = error instanceof Error ? error.name : typeof error;
700
716
  const message = error instanceof Error ? error.message : String(error);
701
- const details =
702
- error instanceof ProviderError
703
- ? providerObservabilityDetails(error)
704
- : undefined;
705
- const emit =
706
- typeof logger === "function" ? logger : defaultProviderServerLogger;
717
+ const details = isProviderError(error) ? providerObservabilityDetails(error) : undefined;
718
+ const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
707
719
  emit({
708
720
  level: status >= 500 ? "error" : "warn",
709
721
  event: "provider_request_failed",
@@ -716,7 +728,7 @@ function logProviderError(
716
728
  code,
717
729
  errorClass,
718
730
  message,
719
- ...(error instanceof TransportError && error.upstreamStatus
731
+ ...(isTransportError(error) && error.upstreamStatus
720
732
  ? { upstreamStatus: error.upstreamStatus }
721
733
  : {}),
722
734
  ...(details
@@ -738,8 +750,7 @@ function logProviderCleanupError(
738
750
  resource: "browser" | "stealth",
739
751
  error: unknown,
740
752
  ): void {
741
- const emit =
742
- typeof logger === "function" ? logger : defaultProviderServerLogger;
753
+ const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
743
754
  const errorClass = error instanceof Error ? error.name : typeof error;
744
755
  const message = error instanceof Error ? error.message : String(error);
745
756
  emit({
@@ -764,8 +775,7 @@ function logProviderSuccess(
764
775
  status: number,
765
776
  cost: ProviderRequestCost,
766
777
  ): void {
767
- const emit =
768
- typeof logger === "function" ? logger : defaultProviderServerLogger;
778
+ const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
769
779
  emit({
770
780
  level: "info",
771
781
  event: "provider_request_completed",
@@ -811,18 +821,13 @@ function toJsonSuccessResponse(
811
821
  };
812
822
  }
813
823
 
814
- function isAsyncIterable<T = unknown>(
815
- value: unknown,
816
- ): value is AsyncIterable<T> {
824
+ function isAsyncIterable<T = unknown>(value: unknown): value is AsyncIterable<T> {
817
825
  if (!value || typeof value !== "object") return false;
818
826
  const iterator = Reflect.get(value, Symbol.asyncIterator);
819
827
  return typeof iterator === "function";
820
828
  }
821
829
 
822
- function responseWithCleanup(
823
- response: Response,
824
- cleanup: RequestCleanup,
825
- ): Response {
830
+ function responseWithCleanup(response: Response, cleanup: RequestCleanup): Response {
826
831
  if (!response.body) {
827
832
  void cleanup();
828
833
  return response;
@@ -871,10 +876,7 @@ async function validateSseEvent(
871
876
  const transport = getSseTransport(operation);
872
877
  const schema = transport?.events?.[event.event];
873
878
  if (!schema) {
874
- if (
875
- event.event === APIFUSE_STREAM_ERROR_EVENT ||
876
- event.event === APIFUSE_STREAM_DONE_EVENT
877
- ) {
879
+ if (event.event === APIFUSE_STREAM_ERROR_EVENT || event.event === APIFUSE_STREAM_DONE_EVENT) {
878
880
  return event;
879
881
  }
880
882
  throw new ProviderError(
@@ -887,11 +889,7 @@ async function validateSseEvent(
887
889
  },
888
890
  );
889
891
  }
890
- const data = await parseSchema(
891
- schema,
892
- event.data,
893
- `transport.events.${event.event}`,
894
- );
892
+ const data = await parseSchema(schema, event.data, `transport.events.${event.event}`);
895
893
  return { ...event, data };
896
894
  }
897
895
 
@@ -911,8 +909,7 @@ function assertStreamPayloadWithinLimit(
911
909
  throw new ProviderError(
912
910
  `Stream ${kind} exceeded declared byte limit (${actualBytes} > ${maxBytes}).`,
913
911
  {
914
- code:
915
- kind === "event" ? "STREAM_EVENT_TOO_LARGE" : "STREAM_CHUNK_TOO_LARGE",
912
+ code: kind === "event" ? "STREAM_EVENT_TOO_LARGE" : "STREAM_CHUNK_TOO_LARGE",
916
913
  retryable: false,
917
914
  category: "input_validation",
918
915
  fix:
@@ -957,15 +954,10 @@ function toSseResponse(
957
954
  const validated = await validateSseEvent(operation, next.value);
958
955
  const encodedEvent = encodeSseEvent(validated);
959
956
  const encodedBytes = encoder.encode(encodedEvent);
960
- assertStreamPayloadWithinLimit(
961
- encodedBytes.byteLength,
962
- transport?.maxEventBytes,
963
- "event",
964
- );
957
+ assertStreamPayloadWithinLimit(encodedBytes.byteLength, transport?.maxEventBytes, "event");
965
958
  controller.enqueue(encodedBytes);
966
959
  } catch (error) {
967
- const message =
968
- error instanceof Error ? error.message : "Stream failed";
960
+ const message = error instanceof Error ? error.message : "Stream failed";
969
961
  controller.enqueue(
970
962
  encoder.encode(
971
963
  encodeSseEvent(
@@ -1012,11 +1004,7 @@ function enforceStreamChunkLimit(
1012
1004
  return;
1013
1005
  }
1014
1006
  if (value) {
1015
- assertStreamPayloadWithinLimit(
1016
- byteLength(value),
1017
- maxChunkBytes,
1018
- "chunk",
1019
- );
1007
+ assertStreamPayloadWithinLimit(byteLength(value), maxChunkBytes, "chunk");
1020
1008
  controller.enqueue(value);
1021
1009
  }
1022
1010
  } catch (error) {
@@ -1036,10 +1024,7 @@ function toStreamingResponse(
1036
1024
  requestId?: string,
1037
1025
  ): Response {
1038
1026
  const transport = operation.transport?.kind ?? "json";
1039
- if (
1040
- transport === "sse" &&
1041
- (result instanceof Response || result instanceof ReadableStream)
1042
- ) {
1027
+ if (transport === "sse" && (result instanceof Response || result instanceof ReadableStream)) {
1043
1028
  void cleanup();
1044
1029
  throw new ProviderError(
1045
1030
  "SSE operations must return an AsyncIterable of typed stream.event(...) values.",
@@ -1053,20 +1038,13 @@ function toStreamingResponse(
1053
1038
  }
1054
1039
  if (result instanceof Response) {
1055
1040
  const httpTransport = getHttpStreamTransport(operation);
1056
- if (
1057
- httpTransport &&
1058
- result.body &&
1059
- httpTransport?.maxChunkBytes !== undefined
1060
- ) {
1041
+ if (httpTransport && result.body && httpTransport?.maxChunkBytes !== undefined) {
1061
1042
  return responseWithCleanup(
1062
- new Response(
1063
- enforceStreamChunkLimit(result.body, httpTransport.maxChunkBytes),
1064
- {
1065
- headers: result.headers,
1066
- status: result.status,
1067
- statusText: result.statusText,
1068
- },
1069
- ),
1043
+ new Response(enforceStreamChunkLimit(result.body, httpTransport.maxChunkBytes), {
1044
+ headers: result.headers,
1045
+ status: result.status,
1046
+ statusText: result.statusText,
1047
+ }),
1070
1048
  cleanup,
1071
1049
  );
1072
1050
  }
@@ -1086,8 +1064,7 @@ function toStreamingResponse(
1086
1064
  : {
1087
1065
  "Content-Type":
1088
1066
  operation.transport?.kind === "http-stream"
1089
- ? (operation.transport.contentType ??
1090
- "application/octet-stream")
1067
+ ? (operation.transport.contentType ?? "application/octet-stream")
1091
1068
  : "application/octet-stream",
1092
1069
  },
1093
1070
  }),
@@ -1107,18 +1084,14 @@ function toStreamingResponse(
1107
1084
  );
1108
1085
  }
1109
1086
 
1110
- function getSseTransport(
1111
- operation: OperationDefinition,
1112
- ): OperationSseTransport | undefined {
1087
+ function getSseTransport(operation: OperationDefinition): OperationSseTransport | undefined {
1113
1088
  return operation.transport?.kind === "sse" ? operation.transport : undefined;
1114
1089
  }
1115
1090
 
1116
1091
  function getHttpStreamTransport(
1117
1092
  operation: OperationDefinition,
1118
1093
  ): OperationHttpStreamTransport | undefined {
1119
- return operation.transport?.kind === "http-stream"
1120
- ? operation.transport
1121
- : undefined;
1094
+ return operation.transport?.kind === "http-stream" ? operation.transport : undefined;
1122
1095
  }
1123
1096
 
1124
1097
  function toAuthFlowResponse(
@@ -1139,9 +1112,7 @@ function toAuthFlowResponse(
1139
1112
  };
1140
1113
  }
1141
1114
 
1142
- function authFlowLocaleFromHeaders(
1143
- headers?: Record<string, string>,
1144
- ): ProviderLocale {
1115
+ function authFlowLocaleFromHeaders(headers?: Record<string, string>): ProviderLocale {
1145
1116
  const header = Object.entries(headers ?? {}).find(
1146
1117
  ([key]) => key.toLowerCase() === "accept-language",
1147
1118
  )?.[1];
@@ -1159,9 +1130,7 @@ function isAuthFlowLocale(value: string | undefined): value is ProviderLocale {
1159
1130
  }
1160
1131
 
1161
1132
  function isAuthTurn(value: unknown): value is AuthTurn {
1162
- return (
1163
- !!value && typeof value === "object" && "kind" in value && "turnId" in value
1164
- );
1133
+ return !!value && typeof value === "object" && "kind" in value && "turnId" in value;
1165
1134
  }
1166
1135
 
1167
1136
  function loadAuthFlowLocaleCatalogs(
@@ -1198,10 +1167,7 @@ function materializeAuthFlowTurn(
1198
1167
  });
1199
1168
  }
1200
1169
 
1201
- function withAuthRequestHeaders(
1202
- request: AuthFlowRequest,
1203
- headers: Headers,
1204
- ): AuthFlowRequest {
1170
+ function withAuthRequestHeaders(request: AuthFlowRequest, headers: Headers): AuthFlowRequest {
1205
1171
  return {
1206
1172
  ...request,
1207
1173
  headers: {
@@ -1219,17 +1185,9 @@ async function handleOperation(
1219
1185
  state: ProviderRuntimeState = createUnsupportedProviderRuntimeState(),
1220
1186
  proxyTelemetry?: ProxyTelemetryCollector,
1221
1187
  ): Promise<Response | OperationResponse> {
1222
- const ctx = createProviderContext(
1223
- provider,
1224
- request,
1225
- operationId,
1226
- options,
1227
- state,
1228
- proxyTelemetry,
1229
- );
1188
+ const ctx = createProviderContext(provider, request, operationId, options, state, proxyTelemetry);
1230
1189
  const operation = provider.operations[operationId];
1231
- const streaming =
1232
- operation?.transport?.kind && operation.transport.kind !== "json";
1190
+ const streaming = operation?.transport?.kind && operation.transport.kind !== "json";
1233
1191
  let cleanupCalled = false;
1234
1192
  const cleanup = async () => {
1235
1193
  if (cleanupCalled) return;
@@ -1260,12 +1218,7 @@ async function handleOperation(
1260
1218
  }
1261
1219
  };
1262
1220
  try {
1263
- const result = await executeOperation(
1264
- provider,
1265
- operationId,
1266
- ctx,
1267
- request.input,
1268
- );
1221
+ const result = await executeOperation(provider, operationId, ctx, request.input);
1269
1222
  if (streaming && operation) {
1270
1223
  return toStreamingResponse(operation, result, cleanup, request.requestId);
1271
1224
  }
@@ -1309,13 +1262,16 @@ async function handleAuthFlow(
1309
1262
  });
1310
1263
  }
1311
1264
 
1312
- const { context, getPatch } = createAuthFlowContext(
1313
- provider,
1314
- request,
1315
- options,
1316
- signal,
1317
- );
1265
+ // Same SDK-owned gate as executeOperation: OAuth/credentials ceremonies
1266
+ // depend on declared secrets (client ids/secrets), so fail structured before
1267
+ // any flow code runs instead of at whatever point the ceremony first reads
1268
+ // the env. `abort` stays exempt: a user must always be able to cancel a
1269
+ // stranded flow even when provisioning is broken.
1270
+ const { context, getPatch } = createAuthFlowContext(provider, request, options, signal);
1318
1271
  try {
1272
+ if (route !== "abort") {
1273
+ assertRequiredSecretsPresent(provider, context.env);
1274
+ }
1319
1275
  const result =
1320
1276
  route === "start"
1321
1277
  ? await flow.start(context)
@@ -1370,6 +1326,24 @@ export function createServerApp(
1370
1326
  allowMemoryFallback: options.allowMemoryStateFallback === true,
1371
1327
  });
1372
1328
 
1329
+ // Boot-time visibility for unprovisioned declared secrets: emit a structured
1330
+ // warn so deploy tooling/alerting sees the gap the moment the pod boots,
1331
+ // instead of discovering it request-by-request. Deliberately log-only — a
1332
+ // boot crash would trade a structured MISSING_SECRET signal for
1333
+ // CrashLoopBackOff. Requests still fail closed via the executeOperation gate.
1334
+ const missingSecretsAtBoot = listMissingRequiredSecrets(
1335
+ provider,
1336
+ createEnvContext(provider.secrets?.map((secret) => secret.name)),
1337
+ );
1338
+ if (missingSecretsAtBoot.length > 0) {
1339
+ logger({
1340
+ level: "warn",
1341
+ event: "provider_secrets_missing",
1342
+ providerId: provider.id,
1343
+ missingSecrets: missingSecretsAtBoot,
1344
+ });
1345
+ }
1346
+
1373
1347
  app.notFound((c) =>
1374
1348
  c.json(
1375
1349
  {
@@ -1462,17 +1436,8 @@ export function createServerApp(
1462
1436
  .clone()
1463
1437
  .json()
1464
1438
  .catch(() => undefined);
1465
- const body = withAuthRequestHeaders(
1466
- AuthFlowRequestSchema.parse(rawBody),
1467
- c.req.raw.headers,
1468
- );
1469
- const response = await handleAuthFlow(
1470
- provider,
1471
- body,
1472
- "start",
1473
- options,
1474
- c.req.raw.signal,
1475
- );
1439
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1440
+ const response = await handleAuthFlow(provider, body, "start", options, c.req.raw.signal);
1476
1441
  logProviderSuccess(
1477
1442
  logger,
1478
1443
  provider,
@@ -1508,17 +1473,8 @@ export function createServerApp(
1508
1473
  .clone()
1509
1474
  .json()
1510
1475
  .catch(() => undefined);
1511
- const body = withAuthRequestHeaders(
1512
- AuthFlowRequestSchema.parse(rawBody),
1513
- c.req.raw.headers,
1514
- );
1515
- const response = await handleAuthFlow(
1516
- provider,
1517
- body,
1518
- "continue",
1519
- options,
1520
- c.req.raw.signal,
1521
- );
1476
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1477
+ const response = await handleAuthFlow(provider, body, "continue", options, c.req.raw.signal);
1522
1478
  logProviderSuccess(
1523
1479
  logger,
1524
1480
  provider,
@@ -1554,17 +1510,8 @@ export function createServerApp(
1554
1510
  .clone()
1555
1511
  .json()
1556
1512
  .catch(() => undefined);
1557
- const body = withAuthRequestHeaders(
1558
- AuthFlowRequestSchema.parse(rawBody),
1559
- c.req.raw.headers,
1560
- );
1561
- const response = await handleAuthFlow(
1562
- provider,
1563
- body,
1564
- "poll",
1565
- options,
1566
- c.req.raw.signal,
1567
- );
1513
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1514
+ const response = await handleAuthFlow(provider, body, "poll", options, c.req.raw.signal);
1568
1515
  logProviderSuccess(
1569
1516
  logger,
1570
1517
  provider,
@@ -1600,17 +1547,8 @@ export function createServerApp(
1600
1547
  .clone()
1601
1548
  .json()
1602
1549
  .catch(() => undefined);
1603
- const body = withAuthRequestHeaders(
1604
- AuthFlowRequestSchema.parse(rawBody),
1605
- c.req.raw.headers,
1606
- );
1607
- const response = await handleAuthFlow(
1608
- provider,
1609
- body,
1610
- "refresh",
1611
- options,
1612
- c.req.raw.signal,
1613
- );
1550
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1551
+ const response = await handleAuthFlow(provider, body, "refresh", options, c.req.raw.signal);
1614
1552
  logProviderSuccess(
1615
1553
  logger,
1616
1554
  provider,
@@ -1646,17 +1584,8 @@ export function createServerApp(
1646
1584
  .clone()
1647
1585
  .json()
1648
1586
  .catch(() => undefined);
1649
- const body = withAuthRequestHeaders(
1650
- AuthFlowRequestSchema.parse(rawBody),
1651
- c.req.raw.headers,
1652
- );
1653
- const response = await handleAuthFlow(
1654
- provider,
1655
- body,
1656
- "abort",
1657
- options,
1658
- c.req.raw.signal,
1659
- );
1587
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1588
+ const response = await handleAuthFlow(provider, body, "abort", options, c.req.raw.signal);
1660
1589
  logProviderSuccess(
1661
1590
  logger,
1662
1591
  provider,
@@ -1731,12 +1660,9 @@ export async function serve(
1731
1660
  const bunRuntime = getBunServeRuntime();
1732
1661
 
1733
1662
  if (bunRuntime === undefined) {
1734
- throw new ProviderError(
1735
- "Bun runtime is required to start the provider server",
1736
- {
1737
- code: "RUNTIME_UNSUPPORTED",
1738
- },
1739
- );
1663
+ throw new ProviderError("Bun runtime is required to start the provider server", {
1664
+ code: "RUNTIME_UNSUPPORTED",
1665
+ });
1740
1666
  }
1741
1667
 
1742
1668
  const app = createServerApp(provider, {
@@ -1758,6 +1684,7 @@ export async function serve(
1758
1684
  const selfTestApp = createSelfTestApp(provider, {
1759
1685
  secrets: selfTestSecrets,
1760
1686
  invoke: createSelfTestInvoke(app),
1687
+ authFlow: createSelfTestAuthFlowInvoke(app),
1761
1688
  });
1762
1689
  bunRuntime.serve({
1763
1690
  port: options.selfTestPort ?? resolveSelfTestPort(),