@apifuse/provider-sdk 2.2.0-beta.61 → 2.2.0-beta.63

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 (258) hide show
  1. package/AUTHORING.md +268 -9
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +1 -0
  4. package/bin/apifuse-check.ts +84 -2
  5. package/bin/apifuse-dev.ts +11 -31
  6. package/bin/apifuse-migrate-deployment.ts +200 -0
  7. package/bin/apifuse-pack-check.ts +11 -4
  8. package/bin/apifuse-pack-types.ts +191 -4
  9. package/bin/apifuse-record.ts +44 -32
  10. package/bin/apifuse-test.ts +3 -1
  11. package/dist/cli/commands.d.ts +1 -1
  12. package/dist/cli/commands.js +11 -0
  13. package/dist/cli/deployment-intent.d.ts +178 -0
  14. package/dist/cli/deployment-intent.js +277 -0
  15. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  16. package/dist/cli/migrate-deployment-intent.js +1552 -0
  17. package/dist/cli/migrate-provider-shape.d.ts +5 -1
  18. package/dist/cli/migrate-provider-shape.js +4 -2
  19. package/dist/cli/resolver-runtime.d.ts +21 -0
  20. package/dist/cli/resolver-runtime.js +31 -0
  21. package/dist/define.d.ts +3 -0
  22. package/dist/define.js +5 -0
  23. package/dist/dev.d.ts +3 -1
  24. package/dist/dev.js +6 -1
  25. package/dist/engine.d.ts +35 -1
  26. package/dist/engine.js +21 -2
  27. package/dist/error-resolution.d.ts +1 -0
  28. package/dist/error-resolution.js +54 -0
  29. package/dist/errors.d.ts +39 -1
  30. package/dist/errors.js +50 -0
  31. package/dist/handle-meta.d.ts +18 -2
  32. package/dist/handle-meta.js +30 -2
  33. package/dist/handle.d.ts +33 -12
  34. package/dist/handle.js +69 -24
  35. package/dist/i18n/error-messages.d.ts +86 -0
  36. package/dist/i18n/error-messages.js +192 -0
  37. package/dist/i18n/index.d.ts +1 -0
  38. package/dist/i18n/index.js +1 -0
  39. package/dist/i18n/keys.d.ts +12 -0
  40. package/dist/i18n/keys.js +76 -5
  41. package/dist/index.d.ts +10 -2
  42. package/dist/index.js +9 -1
  43. package/dist/lint.d.ts +10 -0
  44. package/dist/lint.js +440 -57
  45. package/dist/provider.d.ts +7 -5
  46. package/dist/provider.js +4 -2
  47. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  48. package/dist/runtime/browser-telemetry-binding.js +83 -0
  49. package/dist/runtime/browser-telemetry.d.ts +76 -0
  50. package/dist/runtime/browser-telemetry.js +165 -0
  51. package/dist/runtime/browser.d.ts +3 -0
  52. package/dist/runtime/browser.js +208 -47
  53. package/dist/runtime/cache-telemetry.d.ts +71 -0
  54. package/dist/runtime/cache-telemetry.js +177 -0
  55. package/dist/runtime/cache.js +67 -7
  56. package/dist/runtime/capability-response.d.ts +3 -0
  57. package/dist/runtime/capability-response.js +56 -0
  58. package/dist/runtime/capability-telemetry.d.ts +46 -0
  59. package/dist/runtime/capability-telemetry.js +68 -0
  60. package/dist/runtime/diagnostic-env.d.ts +3 -0
  61. package/dist/runtime/diagnostic-env.js +10 -1
  62. package/dist/runtime/engine-mode.d.ts +74 -0
  63. package/dist/runtime/engine-mode.js +152 -0
  64. package/dist/runtime/handle.js +15 -10
  65. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  66. package/dist/runtime/http-telemetry-binding.js +152 -0
  67. package/dist/runtime/http-telemetry-guard.d.ts +16 -0
  68. package/dist/runtime/http-telemetry-guard.js +202 -0
  69. package/dist/runtime/http-telemetry.d.ts +104 -0
  70. package/dist/runtime/http-telemetry.js +190 -0
  71. package/dist/runtime/http.d.ts +31 -0
  72. package/dist/runtime/http.js +356 -174
  73. package/dist/runtime/instrumentation.js +92 -13
  74. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  75. package/dist/runtime/ocr-telemetry.js +192 -0
  76. package/dist/runtime/ocr.js +30 -8
  77. package/dist/runtime/request-telemetry.d.ts +2 -0
  78. package/dist/runtime/request-telemetry.js +2 -1
  79. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  80. package/dist/runtime/resolver-runtime-options.js +33 -0
  81. package/dist/runtime/resolver-shared.d.ts +3 -2
  82. package/dist/runtime/resolver-shared.js +4 -4
  83. package/dist/runtime/resolver-telemetry.d.ts +1 -0
  84. package/dist/runtime/resolver-telemetry.js +1 -1
  85. package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
  86. package/dist/runtime/resolver-vendors/browser.js +46 -0
  87. package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
  88. package/dist/runtime/resolver-vendors/hosts.js +35 -0
  89. package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
  90. package/dist/runtime/resolver-vendors/types.d.ts +5 -4
  91. package/dist/runtime/resolver.d.ts +4 -1
  92. package/dist/runtime/resolver.js +46 -13
  93. package/dist/runtime/state-telemetry.d.ts +79 -0
  94. package/dist/runtime/state-telemetry.js +194 -0
  95. package/dist/runtime/state.d.ts +3 -0
  96. package/dist/runtime/state.js +26 -7
  97. package/dist/runtime/stealth-cookies.d.ts +5 -1
  98. package/dist/runtime/stealth-cookies.js +21 -1
  99. package/dist/runtime/stealth-owned-headers.d.ts +26 -0
  100. package/dist/runtime/stealth-owned-headers.js +37 -0
  101. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  102. package/dist/runtime/stealth-telemetry.js +179 -0
  103. package/dist/runtime/stealth.d.ts +4 -1
  104. package/dist/runtime/stealth.js +228 -85
  105. package/dist/runtime/stt-telemetry.d.ts +77 -0
  106. package/dist/runtime/stt-telemetry.js +176 -0
  107. package/dist/runtime/stt.js +15 -6
  108. package/dist/server/error-stack-frames.d.ts +35 -0
  109. package/dist/server/error-stack-frames.js +219 -0
  110. package/dist/server/index.d.ts +9 -0
  111. package/dist/server/index.js +5 -0
  112. package/dist/server/sdk-version.d.ts +3 -0
  113. package/dist/server/sdk-version.js +19 -0
  114. package/dist/server/self-test.js +1 -18
  115. package/dist/server/sensitive-values.d.ts +1 -1
  116. package/dist/server/sensitive-values.js +9 -0
  117. package/dist/server/serve-implementation.d.ts +53 -2
  118. package/dist/server/serve-implementation.js +369 -123
  119. package/dist/server/types.d.ts +1 -0
  120. package/dist/server/types.js +7 -0
  121. package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
  122. package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
  123. package/dist/stateful/stateful-provider-runtime-context.js +1 -0
  124. package/dist/types.d.ts +94 -1
  125. package/package.json +6 -1
  126. package/src/cli/commands.ts +14 -0
  127. package/src/cli/deployment-intent.ts +364 -0
  128. package/src/cli/migrate-deployment-intent.ts +1908 -0
  129. package/src/cli/migrate-provider-shape.ts +10 -4
  130. package/src/cli/resolver-runtime.ts +72 -0
  131. package/src/define.ts +13 -0
  132. package/src/dev.ts +9 -2
  133. package/src/engine.ts +38 -2
  134. package/src/error-resolution.ts +58 -0
  135. package/src/errors.ts +77 -1
  136. package/src/handle-meta.ts +39 -4
  137. package/src/handle.ts +121 -38
  138. package/src/i18n/error-messages.ts +255 -0
  139. package/src/i18n/index.ts +1 -0
  140. package/src/i18n/keys.ts +80 -6
  141. package/src/index.ts +91 -0
  142. package/src/lint.ts +552 -57
  143. package/src/provider.ts +20 -0
  144. package/src/runtime/browser-telemetry-binding.ts +92 -0
  145. package/src/runtime/browser-telemetry.ts +289 -0
  146. package/src/runtime/browser.ts +249 -65
  147. package/src/runtime/cache-telemetry.ts +247 -0
  148. package/src/runtime/cache.ts +68 -17
  149. package/src/runtime/capability-response.ts +53 -0
  150. package/src/runtime/capability-telemetry.ts +101 -0
  151. package/src/runtime/diagnostic-env.ts +11 -1
  152. package/src/runtime/engine-mode.ts +233 -0
  153. package/src/runtime/handle.ts +20 -10
  154. package/src/runtime/http-telemetry-binding.ts +162 -0
  155. package/src/runtime/http-telemetry-guard.ts +208 -0
  156. package/src/runtime/http-telemetry.ts +292 -0
  157. package/src/runtime/http.ts +457 -204
  158. package/src/runtime/instrumentation.ts +105 -16
  159. package/src/runtime/ocr-telemetry.ts +308 -0
  160. package/src/runtime/ocr.ts +109 -74
  161. package/src/runtime/request-telemetry.ts +2 -1
  162. package/src/runtime/resolver-runtime-options.ts +50 -0
  163. package/src/runtime/resolver-shared.ts +9 -4
  164. package/src/runtime/resolver-telemetry.ts +1 -1
  165. package/src/runtime/resolver-vendors/browser.ts +65 -7
  166. package/src/runtime/resolver-vendors/hosts.ts +43 -0
  167. package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
  168. package/src/runtime/resolver-vendors/types.ts +5 -4
  169. package/src/runtime/resolver.ts +64 -21
  170. package/src/runtime/state-telemetry.ts +298 -0
  171. package/src/runtime/state.ts +46 -45
  172. package/src/runtime/stealth-cookies.ts +24 -1
  173. package/src/runtime/stealth-owned-headers.ts +41 -0
  174. package/src/runtime/stealth-telemetry.ts +304 -0
  175. package/src/runtime/stealth.ts +355 -150
  176. package/src/runtime/stt-telemetry.ts +282 -0
  177. package/src/runtime/stt.ts +93 -71
  178. package/src/server/error-stack-frames.ts +214 -0
  179. package/src/server/index.ts +66 -0
  180. package/src/server/sdk-version.ts +22 -0
  181. package/src/server/self-test.ts +1 -21
  182. package/src/server/sensitive-values.ts +14 -0
  183. package/src/server/serve-implementation.ts +601 -157
  184. package/src/server/types.ts +7 -0
  185. package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
  186. package/src/stateful/stateful-provider-runtime-context.ts +4 -0
  187. package/src/types.ts +99 -1
  188. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
  189. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
  190. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
  191. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
  192. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
  193. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
  194. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
  195. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
  196. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
  197. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
  198. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
  199. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
  200. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
  201. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
  202. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
  203. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
  204. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
  205. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
  206. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
  207. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
  208. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
  209. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
  210. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
  211. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
  212. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
  213. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
  214. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
  215. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
  216. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
  217. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
  218. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
  219. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
  220. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
  221. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
  222. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
  223. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
  224. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
  225. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
  226. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
  227. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
  244. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
  245. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
  246. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
  247. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
  248. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
  249. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
  250. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
  251. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
  252. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
  253. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
  254. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
  255. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
  256. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
  257. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
  258. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
@@ -1,4 +1,4 @@
1
- import { NativeTelemetryCollector } from "../runtime/native-telemetry.js";
1
+ import { bindCapabilityRoot } from "../runtime/capability-telemetry.js";
2
2
  import { STATUS_CODES } from "node:http";
3
3
  import { readDiagnosticEnv, withDiagnosticEnv } from "../runtime/diagnostic-env.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -10,15 +10,20 @@ import { Hono } from "hono";
10
10
  import { z } from "zod";
11
11
  import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
12
12
  import { validateFailClosedDeclaration } from "../declaration-validation.js";
13
- import { createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, isEngineOwnedEnvName, readEngineProxyCredentials, } from "../engine.js";
13
+ import { ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, isEngineOwnedEnvName, readEngineProxyCredentials, } from "../engine.js";
14
+ import { createEngineForMode, isUnavailableProviderEngine, resolveProviderEngineMode, } from "../runtime/engine-mode.js";
15
+ import { SDK_VERSION } from "./sdk-version.js";
14
16
  import { safeProviderErrorObservability } from "../error-observability.js";
17
+ import { providerErrorStackFrames } from "./error-stack-frames.js";
15
18
  import { SDK_OWNED_PROVIDER_ERROR_CODES, SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "../error-resolution.js";
16
19
  import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, SDKError, } from "../errors.js";
17
20
  import { sanitizeDiagnosticText } from "../fixture-sanitization.js";
18
21
  import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog.js";
22
+ import { acceptLanguageHeaderValue, DEFAULT_PROVIDER_ERROR_LOCALE, localizeProviderErrorText, PROVIDER_ERROR_LOCALES, resolveProviderErrorLocale, } from "../i18n/error-messages.js";
19
23
  import { categoryForStatus, isRetryableCategory, PROVIDER_OBSERVABILITY_TAXONOMY_VERSION, sourceForCategory, } from "../observability.js";
20
24
  import { createScratchpad } from "../runtime/auth-flow.js";
21
25
  import { createProviderCache } from "../runtime/cache.js";
26
+ import { bindCacheTelemetry, CacheTelemetryCollector } from "../runtime/cache-telemetry.js";
22
27
  import { createHandleContext } from "../runtime/handle.js";
23
28
  import { createCredentialContext } from "../runtime/credential.js";
24
29
  import { bindDiagnosticSensitiveRegistry, compileProcessDiagnosticSensitiveValues, createDiagnosticRedactor, REDACTION_FAILED, isDiagnosticRedactionSuppressed, bindCriticalDiagnosticRedactor, redactCriticalDiagnosticText, redactDiagnosticText, registerDiagnosticSensitiveValues, } from "../runtime/diagnostic-redactor.js";
@@ -26,18 +31,28 @@ import { createCeremonyEgressLeaseRuntime, ENGINE_CEREMONY_EGRESS_LEASE, } from
26
31
  import { createEnvContext } from "../runtime/env.js";
27
32
  import { executeOperation } from "../runtime/executor.js";
28
33
  import { createHttpClient } from "../runtime/http.js";
34
+ import { NativeTelemetryCollector } from "../runtime/native-telemetry.js";
35
+ import { bindHttpTelemetry, isSupportedHttpBinding, warnUnsupportedHttpBinding, } from "../runtime/http-telemetry-binding.js";
36
+ import { HttpTelemetryCollector } from "../runtime/http-telemetry.js";
29
37
  import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
30
38
  import { createOcrClientFromEnv } from "../runtime/ocr.js";
39
+ import { bindOcrTelemetry, OcrTelemetryCollector } from "../runtime/ocr-telemetry.js";
31
40
  import { getProviderBaseUrl } from "../runtime/provider.js";
32
41
  import { PROXY_AUTH_IP_DENIED_CODE, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_EXHAUSTED_CODE, } from "../runtime/proxy-errors.js";
33
42
  import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector, } from "../runtime/proxy-telemetry.js";
34
43
  import { closedEnum, RequestTelemetry, tenantOpaqueKeys, } from "../runtime/request-telemetry.js";
35
44
  import { bindResolverTelemetry, createUnsupportedResolverClient, inheritResolverTelemetryBinding, } from "../runtime/resolver-shared.js";
36
45
  import { ResolverTelemetryCollector, } from "../runtime/resolver-telemetry.js";
46
+ import { createResolverRuntimeOptions, resolveNativeProxyPolicy, } from "../runtime/resolver-runtime-options.js";
37
47
  import { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRET_CODE, } from "../runtime/secrets.js";
38
48
  import { createProviderRuntimeStateFromEnv, createUnsupportedProviderRuntimeState, } from "../runtime/state.js";
49
+ import { StateTelemetryCollector, instrumentProviderRuntimeState, } from "../runtime/state-telemetry.js";
39
50
  import { StealthCookieJar } from "../runtime/stealth-cookies.js";
51
+ import { StealthTelemetryCollector } from "../runtime/stealth-telemetry.js";
52
+ import { BrowserTelemetryCollector } from "../runtime/browser-telemetry.js";
53
+ import { bindBrowserTelemetry, isSupportedBrowserBinding, warnUnsupportedBrowserBinding, } from "../runtime/browser-telemetry-binding.js";
40
54
  import { createSttClientFromEnv } from "../runtime/stt.js";
55
+ import { bindSttTelemetry, SttTelemetryCollector } from "../runtime/stt-telemetry.js";
41
56
  import { createTraceContext, getTraceRecorder, updateTraceContextExportMetadata, } from "../runtime/trace.js";
42
57
  import { resolveTraceConfigFromEnv } from "../runtime/trace-config.js";
43
58
  import { parseSchema } from "../schema.js";
@@ -81,18 +96,7 @@ function providerErrorCode(error) {
81
96
  return undefined;
82
97
  }
83
98
  }
84
- const AUTH_FLOW_LOCALES = ["en", "ko", "ja"];
85
99
  const retryResponseMeta = new WeakMap();
86
- const RETRY_LAST_ERROR_CODES = [
87
- "transport_cancelled",
88
- "transport_timeout",
89
- "transport_network_error",
90
- "upstream_http_error",
91
- "other",
92
- ];
93
- function tenantRetryLastErrorCode(value) {
94
- return RETRY_LAST_ERROR_CODES.find((candidate) => candidate === value) ?? "other";
95
- }
96
100
  const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
97
101
  const STATEFUL_FORWARDING_SOURCE_POD_HEADER = "x-apifuse-stateful-source-pod";
98
102
  const DEFAULT_STATEFUL_FORWARDING_MAX_SKEW_MS = 5 * 60_000;
@@ -121,6 +125,18 @@ export const ProviderServerStatefulForwardEnvelopeSchema = z
121
125
  }).strict(),
122
126
  })
123
127
  .strict();
128
+ // Inbound compatibility boundary for rolling deploys, mirroring the outbound
129
+ // one in stateful-provider-owner-forwarder.ts: emitted envelopes stay strict
130
+ // (above), while a received envelope's nested operation request keeps the
131
+ // gateway route's tolerance and strips unknown keys. A source pod on a newer
132
+ // SDK carrying an additive OperationRequestSchema field (tenantId, #164) must
133
+ // not be rejected with STATEFUL_FORWARDING_ENVELOPE_INVALID by an owner pod
134
+ // that predates the field: the rejection is terminal for the operation and the
135
+ // two pods coexist for the whole rolling update. The envelope's own keys stay
136
+ // strict on both sides, because they are protocol rather than payload.
137
+ const ReceivedStatefulForwardEnvelopeSchema = ProviderServerStatefulForwardEnvelopeSchema.extend({
138
+ operationRequest: z.object(ProviderServerStatefulForwardEnvelopeSchema.shape.operationRequest.shape),
139
+ });
124
140
  const require = createRequire(import.meta.url);
125
141
  function normalizedCapabilityCause(error) {
126
142
  if (error instanceof Error)
@@ -335,24 +351,6 @@ function getProviderStealthBaseUrl(provider) {
335
351
  function getProviderStealthProfile(provider) {
336
352
  return provider.stealth ? getStealthProfile(provider.stealth) : undefined;
337
353
  }
338
- /** One option set for ctx.resolver and the automatic SBSD solve, so both share identity scope, cache, proxy intent, and telemetry. */
339
- function createResolverRuntimeOptions(provider, cache, identityScope, proxyPolicy, proxyClientOptions, stealthProfile, telemetry) {
340
- return {
341
- allowedHosts: provider.allowedHosts,
342
- cache,
343
- telemetry,
344
- identityScope,
345
- ...(proxyPolicy
346
- ? {
347
- proxyIntent: {
348
- mode: proxyPolicy.mode,
349
- ...proxyClientOptions,
350
- ...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
351
- },
352
- }
353
- : {}),
354
- };
355
- }
356
354
  /** Shared predicate for the SBSD challenge runtime and the ceremony egress lease. */
357
355
  function declaresAkamaiSbsdResolver(provider) {
358
356
  return provider.resolver?.kinds.some((kind) => kind === "akamai_sbsd") === true;
@@ -375,12 +373,13 @@ function createStealthChallengeDetection(provider, resolverRuntime, resolverOver
375
373
  ? {
376
374
  async solve(challenge, transport, solveSignal) {
377
375
  const resolver = resolverRuntime.bindResolverSignal((resolverOverride
378
- ? bindResolverTelemetry(resolverOverride, resolverOptions.telemetry)
376
+ ? bindResolverTelemetry(resolverOverride, resolverOptions.telemetry, resolverOptions.browserTelemetry)
379
377
  : undefined) ??
380
378
  resolverRuntime.createResolverClientFromEnv(provider.resolver, undefined, {
381
379
  allowedHosts: resolverOptions.allowedHosts,
382
380
  cache: resolverOptions.cache,
383
381
  telemetry: resolverOptions.telemetry,
382
+ browserTelemetry: resolverOptions.browserTelemetry,
384
383
  identityScope: resolverOptions.identityScope,
385
384
  // No proxyIntent: the transport is already bound to the initiating
386
385
  // request's lease, and a second identity resolution would allocate
@@ -473,19 +472,16 @@ function resolveOperationConnectionId(request) {
473
472
  // absent so it can never override a valid id or key a real scope. Requests
474
473
  // without any usable id fall back to the documented missing-connection
475
474
  // sentinel scope instead of scoping context/affinity/state under "".
476
- return (normalizeConnectionId(request.connection?.id) ?? normalizeConnectionId(request.connectionId));
475
+ return normalizeIdentifier(request.connection?.id) ?? normalizeIdentifier(request.connectionId);
477
476
  }
478
- function normalizeConnectionId(id) {
479
- return id === "" ? undefined : id;
477
+ function resolveOperationTenantId(request) {
478
+ // Only the gateway-asserted envelope field is consulted; caller headers can
479
+ // never populate the principal scope. Same rule as connection ids: "" is a
480
+ // malformed identifier, not a principal.
481
+ return normalizeIdentifier(request.tenantId);
480
482
  }
481
- function resolveNativeProxyPolicy(provider) {
482
- if (typeof provider.proxy === "object")
483
- return provider.proxy;
484
- if (provider.proxy === true)
485
- return { mode: "optional" };
486
- if (provider.proxy === false)
487
- return { mode: "disabled" };
488
- return undefined;
483
+ function normalizeIdentifier(id) {
484
+ return id === "" ? undefined : id;
489
485
  }
490
486
  function providerSecretNames(provider) {
491
487
  return (provider.secrets?.map((secret) => secret.name) ?? []).filter((name) => !isEngineOwnedEnvName(name));
@@ -503,12 +499,12 @@ function createProviderContext(provider, request, operationId, options, state =
503
499
  engineCredentials: engineProxyCredentials,
504
500
  };
505
501
  const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
506
- const cache = createProviderCache({ providerId: provider.id });
507
- let wrappedContext;
502
+ const cache = createProviderCache(bindCacheTelemetry({ providerId: provider.id }, scope.cacheTelemetry));
508
503
  const { capabilityModules } = options;
509
- const resolverOptions = createResolverRuntimeOptions(provider, cache, resolverIdentityScope, proxyPolicy, proxyClientOptions, stealthProfile, scope.resolverTelemetry);
504
+ const resolverOptions = createResolverRuntimeOptions(provider, cache, resolverIdentityScope, proxyPolicy, proxyClientOptions, stealthProfile, scope.resolverTelemetry, scope.browserTelemetry);
510
505
  const challengeRuntime = createStealthChallengeDetection(provider, capabilityModules.resolver, options.resolver, resolverOptions, scope.trace, signal);
511
506
  const stealthClientOptions = {
507
+ stealthTelemetry: scope.stealthTelemetry,
512
508
  upstream: proxyClientOptions.upstream,
513
509
  affinityKey: proxyClientOptions.affinityKey,
514
510
  telemetry: scope.telemetry.proxy,
@@ -533,11 +529,15 @@ function createProviderContext(provider, request, operationId, options, state =
533
529
  scopes: request.connection?.scopes,
534
530
  values: request.connection?.secrets,
535
531
  });
532
+ const operationTenantId = resolveOperationTenantId(request);
536
533
  const requestContext = {
537
534
  connectionId: resolveOperationConnectionId(request),
535
+ // Absent stays absent: an own property with an undefined value would
536
+ // change the shape of ctx.request for every existing consumer.
537
+ ...(operationTenantId !== undefined ? { tenantId: operationTenantId } : {}),
538
538
  headers: request.headers ?? {},
539
539
  };
540
- const requestState = state.forConnection(requestContext.connectionId);
540
+ const requestState = instrumentProviderRuntimeState(state, scope.stateTelemetry, scope.redact).forConnection(requestContext.connectionId);
541
541
  const bindings = {
542
542
  env,
543
543
  credential,
@@ -546,11 +546,7 @@ function createProviderContext(provider, request, operationId, options, state =
546
546
  http: createHttpClient(baseUrl, {
547
547
  ...proxyClientOptions,
548
548
  ...(signal ? { signal } : {}),
549
- onRetrySummary: (summary) => {
550
- if (summary.attempts <= 1 || !wrappedContext)
551
- return;
552
- retryResponseMeta.set(wrappedContext, summary);
553
- },
549
+ httpTelemetry: scope.httpTelemetry,
554
550
  }),
555
551
  cache,
556
552
  state: requestState,
@@ -567,6 +563,7 @@ function createProviderContext(provider, request, operationId, options, state =
567
563
  requireCdpPool: isProductionProviderBrowserMode(provider),
568
564
  stealth: true,
569
565
  engine: provider.browser?.engine,
566
+ telemetry: scope.browserTelemetry,
570
567
  })
571
568
  : createBrowserStub(),
572
569
  ...(provider.native
@@ -585,15 +582,15 @@ function createProviderContext(provider, request, operationId, options, state =
585
582
  : {}),
586
583
  trace: scope.trace,
587
584
  auth: createAuthStub(),
588
- ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
589
- stt: options.stt ?? createSttClientFromEnv(provider.stt),
585
+ ocr: bindCapabilityRoot(bindOcrTelemetry(options.ocr ?? createOcrClientFromEnv(provider.ocr), scope.ocrTelemetry), scope.run),
586
+ stt: bindCapabilityRoot(bindSttTelemetry(options.stt ?? createSttClientFromEnv(provider.stt), scope.sttTelemetry), scope.run),
590
587
  resolver: capabilityModules.resolver
591
588
  ? capabilityModules.resolver.bindResolverSignal((options.resolver
592
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
589
+ ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry, resolverOptions.browserTelemetry)
593
590
  : undefined) ??
594
591
  capabilityModules.resolver.createResolverClientFromEnv(provider.resolver, undefined, resolverOptions), signal)
595
592
  : bindResolverSignalWithoutRuntime((options.resolver
596
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
593
+ ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry, resolverOptions.browserTelemetry)
597
594
  : undefined) ??
598
595
  createUnsupportedResolverClient("Provider does not declare resolver capability"), signal),
599
596
  handle: createHandleContext({
@@ -607,8 +604,44 @@ function createProviderContext(provider, request, operationId, options, state =
607
604
  }),
608
605
  }),
609
606
  };
610
- const context = wrapWithInstrumentation(options.engine.attach({ provider, bindings }));
611
- wrappedContext = context;
607
+ const candidateHttp = bindings.http;
608
+ const candidateBrowser = bindings.browser;
609
+ const attached = options.engine.attach({ provider, bindings });
610
+ const hostHttp = provider.http ? attached.http : undefined;
611
+ const supportedHostHttp = provider.http && isSupportedHttpBinding(hostHttp);
612
+ if (provider.http && !supportedHostHttp)
613
+ warnUnsupportedHttpBinding();
614
+ const observedHttp = supportedHostHttp && hostHttp !== candidateHttp
615
+ ? bindHttpTelemetry(hostHttp, scope.httpTelemetry)
616
+ : undefined;
617
+ const hostBrowser = provider.runtime === "browser" ? attached.browser : undefined;
618
+ const supportedHostBrowser = provider.runtime === "browser" && isSupportedBrowserBinding(hostBrowser);
619
+ if (provider.runtime === "browser" && !supportedHostBrowser)
620
+ warnUnsupportedBrowserBinding();
621
+ const observedBrowser = supportedHostBrowser && hostBrowser !== candidateBrowser
622
+ ? bindBrowserTelemetry(hostBrowser, scope.browserTelemetry)
623
+ : undefined;
624
+ const contextTarget = observedBrowser || observedHttp
625
+ ? new Proxy(attached, {
626
+ get(target, property, receiver) {
627
+ if (property === "http" && observedHttp)
628
+ return observedHttp;
629
+ if (property === "http" && provider.http && !supportedHostHttp)
630
+ return undefined;
631
+ if (property === "browser" && observedBrowser)
632
+ return observedBrowser;
633
+ return Reflect.get(target, property, receiver);
634
+ },
635
+ })
636
+ : provider.http && !supportedHostHttp
637
+ ? new Proxy(attached, {
638
+ get(target, property, receiver) {
639
+ return property === "http" ? undefined : Reflect.get(target, property, receiver);
640
+ },
641
+ })
642
+ : attached;
643
+ const context = wrapWithInstrumentation(contextTarget);
644
+ retryResponseMeta.set(context, scope.httpTelemetry);
612
645
  return context;
613
646
  }
614
647
  function createFlowContextStore(allowedKeys, initialContext = {}) {
@@ -678,11 +711,12 @@ function createAuthFlowContext(provider, request, route, options, state, scope,
678
711
  throw new SDKError("An egress lease was supplied for an auth flow that does not use a ceremony lease", { code: "EGRESS_LEASE_INVALID" });
679
712
  }
680
713
  const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
681
- const cache = createProviderCache({ providerId: provider.id });
714
+ const cache = createProviderCache(bindCacheTelemetry({ providerId: provider.id }, scope.cacheTelemetry));
682
715
  const { capabilityModules } = options;
683
- const resolverOptions = createResolverRuntimeOptions(provider, cache, resolverIdentityScope, proxyPolicy, proxyClientOptions, stealthProfile, scope.resolverTelemetry);
716
+ const resolverOptions = createResolverRuntimeOptions(provider, cache, resolverIdentityScope, proxyPolicy, proxyClientOptions, stealthProfile, scope.resolverTelemetry, scope.browserTelemetry);
684
717
  const challengeRuntime = createStealthChallengeDetection(provider, capabilityModules.resolver, options.resolver, resolverOptions, scope.trace, signal);
685
718
  const stealthClientOptions = {
719
+ stealthTelemetry: scope.stealthTelemetry,
686
720
  upstream: proxyClientOptions.upstream,
687
721
  affinityKey: proxyClientOptions.affinityKey,
688
722
  telemetry: scope.telemetry.proxy,
@@ -719,13 +753,25 @@ function createAuthFlowContext(provider, request, route, options, state, scope,
719
753
  http: createHttpClient(baseUrl, {
720
754
  ...proxyClientOptions,
721
755
  ...(signal ? { signal } : {}),
756
+ httpTelemetry: scope.httpTelemetry,
722
757
  }),
723
- state: state.forConnection(resolveOperationConnectionId(request)),
758
+ state: instrumentProviderRuntimeState(state, scope.stateTelemetry, scope.redact).forConnection(resolveOperationConnectionId(request)),
724
759
  stealth: stealthBaseUrl
725
760
  ? capabilityModules.stealth
726
761
  ? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
727
762
  : createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
728
763
  : createStealthStub(),
764
+ browser: provider.runtime === "browser"
765
+ ? capabilityModules.browser.createBrowserClient({
766
+ allowedHosts: provider.allowedHosts,
767
+ cdpUrl: readDiagnosticEnv("APIFUSE__CDP_POOL__URL"),
768
+ headless: true,
769
+ requireCdpPool: isProductionProviderBrowserMode(provider),
770
+ stealth: true,
771
+ engine: provider.browser?.engine,
772
+ telemetry: scope.browserTelemetry,
773
+ })
774
+ : createBrowserStub(),
729
775
  ...(provider.native
730
776
  ? {
731
777
  native: {
@@ -746,15 +792,15 @@ function createAuthFlowContext(provider, request, route, options, state, scope,
746
792
  ]),
747
793
  credential,
748
794
  context: flowContextStore.context,
749
- ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
750
- stt: options.stt ?? createSttClientFromEnv(provider.stt),
795
+ ocr: bindCapabilityRoot(bindOcrTelemetry(options.ocr ?? createOcrClientFromEnv(provider.ocr), scope.ocrTelemetry), scope.run),
796
+ stt: bindCapabilityRoot(bindSttTelemetry(options.stt ?? createSttClientFromEnv(provider.stt), scope.sttTelemetry), scope.run),
751
797
  resolver: capabilityModules.resolver
752
798
  ? capabilityModules.resolver.bindResolverSignal((options.resolver
753
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
799
+ ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry, resolverOptions.browserTelemetry)
754
800
  : undefined) ??
755
801
  capabilityModules.resolver.createResolverClientFromEnv(provider.resolver, undefined, resolverOptions), signal)
756
802
  : bindResolverSignalWithoutRuntime((options.resolver
757
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
803
+ ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry, resolverOptions.browserTelemetry)
758
804
  : undefined) ??
759
805
  createUnsupportedResolverClient("Provider does not declare resolver capability"), signal),
760
806
  auth: createAuthFlowHelpers({ signal }),
@@ -818,7 +864,76 @@ function publicErrorSource(error, category) {
818
864
  }
819
865
  return sourceForCategory(category);
820
866
  }
821
- function toErrorResponse(error, requestId, observabilityDetails, redact) {
867
+ function localeKeyOption(error, key) {
868
+ const value = providerErrorOption(error, key);
869
+ return typeof value === "string" ? value : undefined;
870
+ }
871
+ function declaredLocaleKey(declaration, key) {
872
+ const value = declaration?.[key];
873
+ return typeof value === "string" ? value : undefined;
874
+ }
875
+ function providerErrorParams(error) {
876
+ const params = providerErrorOption(error, "params");
877
+ return params !== null && typeof params === "object" && !Array.isArray(params)
878
+ ? params
879
+ : undefined;
880
+ }
881
+ /**
882
+ * Resolution order for one localized envelope field:
883
+ *
884
+ * 1. the throw site's `messageKey`/`fixKey`;
885
+ * 2. the declared `errorCodes[]` entry's key for the thrown code;
886
+ * 3. the derived `errors.<code>.<field>` convention;
887
+ * 4. the English literal the provider threw.
888
+ *
889
+ * Steps 2 and 3 are skipped for SDK-owned failures (transport, Zod, stateful
890
+ * deadline, SDK runtime codes): their text is the SDK's contract, and a
891
+ * provider catalog entry must not be able to relabel one. An explicit key on
892
+ * the thrown error still wins, because that is the provider deliberately
893
+ * describing an error it constructed itself.
894
+ */
895
+ function localizedErrorField(error, field, literal, localization) {
896
+ if (!localization?.catalogs)
897
+ return literal;
898
+ const optionKey = field === "message" ? "messageKey" : "fixKey";
899
+ const candidates = [
900
+ { kind: "key", key: localeKeyOption(error, optionKey) },
901
+ ];
902
+ if (!sdkOwnsErrorResolution(error)) {
903
+ candidates.push({ kind: "key", key: declaredLocaleKey(localization.declaredErrorCode, optionKey) }, { kind: "derived", code: providerErrorCode(error), field });
904
+ }
905
+ return localizeProviderErrorText({
906
+ catalogs: localization.catalogs,
907
+ locale: localization.locale,
908
+ candidates,
909
+ params: providerErrorParams(error),
910
+ fallback: literal,
911
+ });
912
+ }
913
+ /**
914
+ * Reads `accept-language` out of the request envelope's `headers` map.
915
+ *
916
+ * The gateway stamps the negotiated locale there for auth flows (it persists
917
+ * the start locale on the flow state so continue/poll turns stay in one
918
+ * language), and the operation route merges HTTP headers into the same map, so
919
+ * the envelope is the higher-precedence source.
920
+ */
921
+ function requestBodyAcceptLanguage(rawBody) {
922
+ if (!rawBody || typeof rawBody !== "object")
923
+ return undefined;
924
+ const headers = Object.getOwnPropertyDescriptor(rawBody, "headers")?.value;
925
+ if (!headers || typeof headers !== "object" || Array.isArray(headers))
926
+ return undefined;
927
+ return acceptLanguageHeaderValue(headers);
928
+ }
929
+ function errorEnvelopeLocalization(options) {
930
+ return {
931
+ ...(options.catalogs ? { catalogs: options.catalogs } : {}),
932
+ locale: resolveProviderErrorLocale(requestBodyAcceptLanguage(options.rawBody), options.requestHeaders?.get("accept-language")),
933
+ ...(options.declaredErrorCode ? { declaredErrorCode: options.declaredErrorCode } : {}),
934
+ };
935
+ }
936
+ function toErrorResponse(error, requestId, observabilityDetails, redact, localization) {
822
937
  const observability = observabilityDetails;
823
938
  const source = publicErrorSource(error, observability.category);
824
939
  if (error instanceof StatefulRoutingDeadlineError) {
@@ -834,14 +949,17 @@ function toErrorResponse(error, requestId, observabilityDetails, redact) {
834
949
  }
835
950
  if (isProviderError(error)) {
836
951
  const details = providerErrorOption(error, "details");
952
+ const literalFix = providerErrorOption(error, "fix");
953
+ const fix = localizedErrorField(error, "fix", typeof literalFix === "string" && literalFix.length > 0 ? literalFix : undefined, localization);
954
+ const literalMessage = publicProviderErrorMessage(error);
837
955
  return {
838
956
  error: {
839
957
  code: providerErrorCode(error) ?? "provider_error",
840
- message: publicProviderErrorMessage(error),
958
+ message: localizedErrorField(error, "message", literalMessage, localization) ?? literalMessage,
841
959
  ...(requestId ? { requestId } : {}),
842
960
  retryable: observability.retryable,
843
961
  source,
844
- ...(providerErrorOption(error, "fix") ? { fix: providerErrorOption(error, "fix") } : {}),
962
+ ...(fix ? { fix } : {}),
845
963
  ...(details !== undefined ? { details } : {}),
846
964
  },
847
965
  };
@@ -1087,6 +1205,11 @@ function publicProviderErrorMessage(error) {
1087
1205
  return "Request timed out";
1088
1206
  if (providerErrorCode(error) === "transport_network_error")
1089
1207
  return "Network error";
1208
+ // Provider-local fault: no request was issued, so an upstream-flavored
1209
+ // message would misattribute it (category is provider_error).
1210
+ if (providerErrorCode(error) === "http_header_factory_failed") {
1211
+ return "Request header preparation failed";
1212
+ }
1090
1213
  if (providerErrorCode(error) === "upstream_http_error" && error.status) {
1091
1214
  return `Upstream request failed with status ${error.status}`;
1092
1215
  }
@@ -1210,6 +1333,11 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
1210
1333
  const errorClass = error instanceof Error ? error.name : typeof error;
1211
1334
  const message = error instanceof Error ? error.message : String(error);
1212
1335
  const causeChain = providerErrorCauseChain(error, redact);
1336
+ // Masked 500 bodies carry only errorClass, so the log is the sole channel
1337
+ // that can locate the throw. Declared 5xx (502/504) are included too: their
1338
+ // body carries code+message, but frame[0] is still the throw site inside
1339
+ // provider code. 4xx failures keep the lean record.
1340
+ const stack = status >= 500 ? providerErrorStackFrames(error, redact) : undefined;
1213
1341
  const details = observabilityDetails;
1214
1342
  const isUnregisteredProviderErrorCode = status === 500 &&
1215
1343
  isProviderError(error) &&
@@ -1251,6 +1379,7 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
1251
1379
  errorClass: redactDiagnosticText(errorClass, redact),
1252
1380
  message: sanitizeDiagnosticText(redactDiagnosticText(message, redact)),
1253
1381
  ...(causeChain ? { causeChain } : {}),
1382
+ ...(stack ? { stack } : {}),
1254
1383
  ...(providerObservability ? { providerObservability } : {}),
1255
1384
  ...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
1256
1385
  errorCategory: details.category,
@@ -1452,6 +1581,7 @@ function createRequestScope(input) {
1452
1581
  observe: createDiagnosticEnvObserver(input.provider, sensitiveRegistry.add, sensitiveRegistry.has),
1453
1582
  finished: false,
1454
1583
  staticValues: input.staticSensitiveValues,
1584
+ register: sensitiveRegistry.add,
1455
1585
  };
1456
1586
  const traceConfig = withDiagnosticEnv(observeEnv, () => resolveTraceConfigFromEnv());
1457
1587
  const details = {
@@ -1481,10 +1611,24 @@ function createRequestScope(input) {
1481
1611
  const proxyCollector = new ProxyTelemetryCollector();
1482
1612
  const resolverCollector = new ResolverTelemetryCollector({ redact });
1483
1613
  const nativeCollector = new NativeTelemetryCollector({ redact });
1614
+ const httpCollector = new HttpTelemetryCollector({ redact });
1615
+ const stealthCollector = new StealthTelemetryCollector({ redact });
1616
+ const ocrCollector = new OcrTelemetryCollector({ redact });
1617
+ const sttCollector = new SttTelemetryCollector({ redact });
1618
+ const browserCollector = new BrowserTelemetryCollector({ redact });
1619
+ const cacheTelemetry = new CacheTelemetryCollector({ redact });
1620
+ const stateTelemetry = new StateTelemetryCollector({ redact });
1484
1621
  const telemetry = new RequestTelemetry(trace);
1485
1622
  telemetry.register(proxyCollector);
1486
1623
  telemetry.register(resolverCollector);
1487
1624
  telemetry.register(nativeCollector);
1625
+ telemetry.register(httpCollector);
1626
+ telemetry.register(stealthCollector);
1627
+ telemetry.register(browserCollector);
1628
+ telemetry.register(ocrCollector);
1629
+ telemetry.register(sttCollector);
1630
+ telemetry.register(cacheTelemetry);
1631
+ telemetry.register(stateTelemetry);
1488
1632
  let rootRunner = (fn) => fn();
1489
1633
  let resolveRoot;
1490
1634
  const rootTerminal = new Promise((resolve) => {
@@ -1562,6 +1706,13 @@ function createRequestScope(input) {
1562
1706
  telemetry,
1563
1707
  resolverTelemetry: resolverCollector,
1564
1708
  nativeTelemetry: nativeCollector,
1709
+ httpTelemetry: httpCollector,
1710
+ stealthTelemetry: stealthCollector,
1711
+ ocrTelemetry: ocrCollector,
1712
+ sttTelemetry: sttCollector,
1713
+ browserTelemetry: browserCollector,
1714
+ cacheTelemetry,
1715
+ stateTelemetry,
1565
1716
  seedCredentials(rawBody, kind) {
1566
1717
  const harvested = rawRequestCredentials(rawBody, kind);
1567
1718
  sensitiveRegistry.add(harvested.values);
@@ -1743,7 +1894,7 @@ function toJsonSuccessResponse(result, ctx) {
1743
1894
  return new Response(result);
1744
1895
  }
1745
1896
  const cacheMeta = ctx && "cache" in ctx ? ctx.cache.responseMeta() : undefined;
1746
- const retryMeta = ctx ? retryResponseMeta.get(ctx) : undefined;
1897
+ const retry = ctx ? retryResponseMeta.get(ctx)?.toTenantRetryPayload() : undefined;
1747
1898
  const cache = cacheMeta
1748
1899
  ? {
1749
1900
  hit: cacheMeta.hit,
@@ -1753,21 +1904,7 @@ function toJsonSuccessResponse(result, ctx) {
1753
1904
  ...(cacheMeta.source ? { source: closedEnum(cacheMeta.source) } : {}),
1754
1905
  }
1755
1906
  : undefined;
1756
- const retry = retryMeta
1757
- ? {
1758
- attempts: retryMeta.attempts,
1759
- retries: retryMeta.retries,
1760
- ...(retryMeta.preset ? { preset: closedEnum(retryMeta.preset) } : {}),
1761
- transport: closedEnum(retryMeta.transport),
1762
- ...(retryMeta.lastErrorCode
1763
- ? {
1764
- lastErrorCode: closedEnum(tenantRetryLastErrorCode(retryMeta.lastErrorCode)),
1765
- }
1766
- : {}),
1767
- ...(retryMeta.lastStatus ? { lastStatus: retryMeta.lastStatus } : {}),
1768
- }
1769
- : undefined;
1770
- const meta = cacheMeta || retryMeta
1907
+ const meta = cacheMeta || retry
1771
1908
  ? {
1772
1909
  ...(cache
1773
1910
  ? {
@@ -2016,50 +2153,49 @@ function toAuthFlowResponse(result, contextPatch, engine) {
2016
2153
  ...(engine ? { engine } : {}),
2017
2154
  };
2018
2155
  }
2019
- function authFlowLocaleFromHeaders(headers) {
2020
- const header = Object.entries(headers ?? {}).find(([key]) => key.toLowerCase() === "accept-language")?.[1];
2021
- for (const token of (header ?? "").split(",")) {
2022
- const language = token.trim().split(";")[0]?.split("-")[0]?.toLowerCase();
2023
- if (isAuthFlowLocale(language)) {
2024
- return language;
2025
- }
2026
- }
2027
- return "en";
2028
- }
2029
- function isAuthFlowLocale(value) {
2030
- return value === "en" || value === "ko" || value === "ja";
2031
- }
2032
2156
  function isAuthTurn(value) {
2033
2157
  return !!value && typeof value === "object" && "kind" in value && "turnId" in value;
2034
2158
  }
2035
- function loadAuthFlowLocaleCatalogs(provider) {
2159
+ /**
2160
+ * Loads the provider's locale catalogs once, at `createServerApp` time.
2161
+ *
2162
+ * Only the locales that actually exist on disk are read: a provider with
2163
+ * `en`+`ko` but no `ja.json` used to make the whole load throw and silently
2164
+ * disable localization for every turn. Absence of catalogs is not an error —
2165
+ * every localized field falls back to its English literal — but a catalog that
2166
+ * exists and cannot be read is logged once so a packaging mistake is visible at
2167
+ * boot instead of per request.
2168
+ */
2169
+ function resolveProviderLocaleCatalogs(provider, options, logger) {
2170
+ if (options.localeCatalogs)
2171
+ return options.localeCatalogs;
2036
2172
  for (const providerDir of [
2037
2173
  process.cwd(),
2038
2174
  join(process.cwd(), "providers", provider.id),
2039
2175
  join(process.cwd(), "providers-staging", provider.id),
2040
2176
  ]) {
2041
- if (!existsSync(join(providerDir, "locales", "en.json")))
2177
+ const locales = PROVIDER_ERROR_LOCALES.filter((locale) => existsSync(join(providerDir, "locales", `${locale}.json`)));
2178
+ if (!locales.includes(DEFAULT_PROVIDER_ERROR_LOCALE))
2042
2179
  continue;
2043
2180
  try {
2044
- return loadProviderLocaleCatalogs({
2045
- providerDir,
2046
- locales: AUTH_FLOW_LOCALES,
2047
- });
2181
+ return loadProviderLocaleCatalogs({ providerDir, locales });
2048
2182
  }
2049
- catch {
2183
+ catch (error) {
2184
+ logger({
2185
+ level: "warn",
2186
+ event: "provider_locale_catalogs_unavailable",
2187
+ providerId: provider.id,
2188
+ reason: sanitizeDiagnosticText(error instanceof Error ? error.message : String(error)),
2189
+ });
2050
2190
  return undefined;
2051
2191
  }
2052
2192
  }
2053
2193
  return undefined;
2054
2194
  }
2055
- function materializeAuthFlowTurn(provider, request, turn) {
2056
- const catalogs = loadAuthFlowLocaleCatalogs(provider);
2195
+ function materializeAuthFlowTurn(turn, catalogs, locale) {
2057
2196
  if (!catalogs)
2058
2197
  return turn;
2059
- return localizeAuthTurn(turn, {
2060
- catalogs,
2061
- locale: authFlowLocaleFromHeaders(request.headers),
2062
- });
2198
+ return localizeAuthTurn(turn, { catalogs, locale });
2063
2199
  }
2064
2200
  function withAuthRequestHeaders(request, headers) {
2065
2201
  return {
@@ -2138,7 +2274,15 @@ async function handleOperation(provider, request, operationId, options, state =
2138
2274
  await cleanup();
2139
2275
  }
2140
2276
  }
2141
- async function handleAuthFlow(provider, request, route, options, state, scope, signal) {
2277
+ async function handleAuthFlow(provider, request, route, options, state, scope,
2278
+ /**
2279
+ * Negotiated once per request by the route so a successful turn and an error
2280
+ * envelope from the same request can never disagree. `withAuthRequestHeaders`
2281
+ * lets the HTTP header shadow the envelope's `Accept-Language`, but the
2282
+ * gateway stamps the flow's start locale into the envelope precisely so
2283
+ * continue/poll turns stay in one language, so the envelope wins here.
2284
+ */
2285
+ locale, signal) {
2142
2286
  const flow = provider.auth?.flow;
2143
2287
  if (!flow) {
2144
2288
  throw new ProviderError("Auth flow is not configured", {
@@ -2180,7 +2324,7 @@ async function handleAuthFlow(provider, request, route, options, state, scope, s
2180
2324
  !(result instanceof Response) &&
2181
2325
  !(result instanceof ReadableStream) &&
2182
2326
  isAuthTurn(result)
2183
- ? materializeAuthFlowTurn(provider, request, result)
2327
+ ? materializeAuthFlowTurn(result, options.localeCatalogs, locale)
2184
2328
  : result;
2185
2329
  return toAuthFlowResponse(materializedResult, getPatch(), getEngineState());
2186
2330
  }
@@ -2302,7 +2446,7 @@ function operationRequestFromForwardingEnvelope(envelope) {
2302
2446
  };
2303
2447
  }
2304
2448
  function parseStatefulForwardingEnvelope(rawBody, redact) {
2305
- const parsed = ProviderServerStatefulForwardEnvelopeSchema.safeParse(rawBody);
2449
+ const parsed = ReceivedStatefulForwardEnvelopeSchema.safeParse(rawBody);
2306
2450
  if (parsed.success)
2307
2451
  return parsed.data;
2308
2452
  throw new ProviderError("Stateful forwarding envelope is invalid.", {
@@ -2334,12 +2478,59 @@ export function createServerApp(provider, options = {}) {
2334
2478
  validateStatefulServerConfig(runtimeOptions);
2335
2479
  return createServerAppWithCapabilityModules(runtimeProvider, runtimeOptions, loadProviderCapabilityModulesSync(runtimeProvider));
2336
2480
  }
2481
+ /**
2482
+ * Resolve the engine once at boot, before any route exists. An explicit engine
2483
+ * object is used as given; otherwise the resolved mode selects the engine.
2484
+ *
2485
+ * Deliberately never throws for deployment-authored input: a mode this release
2486
+ * cannot serve yields an engine that fails every request and a `/readyz` 503,
2487
+ * because a boot crash would trade a structured signal for CrashLoopBackOff
2488
+ * across the fleet (same trade-off as `provider_secrets_missing` above).
2489
+ */
2490
+ function resolveServerEngine(serverOptions) {
2491
+ const resolution = resolveProviderEngineMode({
2492
+ option: serverOptions.engineMode,
2493
+ engine: serverOptions.engine,
2494
+ });
2495
+ if (serverOptions.engine !== undefined)
2496
+ return { engine: serverOptions.engine, resolution };
2497
+ return { engine: createEngineForMode(resolution.mode), resolution };
2498
+ }
2499
+ /**
2500
+ * `engine_env_ignored` is expected for the whole migration window: the manifest
2501
+ * generator projects the remote engine env fleet-wide before any mode flips, so
2502
+ * escalating on it would turn every healthy boot into a warn line. The
2503
+ * disagreement warnings still escalate.
2504
+ */
2505
+ const ENGINE_MODE_INFO_WARNINGS = new Set(["engine_env_ignored"]);
2506
+ function engineModeLogEvent(provider, resolution, attached) {
2507
+ const escalating = resolution.warnings.filter((warning) => !ENGINE_MODE_INFO_WARNINGS.has(warning));
2508
+ return {
2509
+ level: attached && escalating.length === 0 ? "info" : "warn",
2510
+ event: "provider_engine_mode",
2511
+ providerId: provider.id,
2512
+ mode: resolution.mode,
2513
+ source: resolution.source,
2514
+ deprecated: resolution.deprecated,
2515
+ attached,
2516
+ sdkVersion: SDK_VERSION,
2517
+ ...(provider.runtimeTarget === undefined ? {} : { runtimeTarget: provider.runtimeTarget }),
2518
+ ...(resolution.warnings.length === 0 ? {} : { warnings: resolution.warnings }),
2519
+ };
2520
+ }
2337
2521
  function createServerAppWithCapabilityModules(provider, serverOptions, capabilityModules) {
2522
+ const engineSelection = resolveServerEngine(serverOptions);
2523
+ // Read the provider's locale catalogs once here rather than per turn: the
2524
+ // previous auth-flow loader re-read all three JSON files on every turn and
2525
+ // threw the whole map away when one locale file was absent.
2526
+ const localeCatalogs = resolveProviderLocaleCatalogs(provider, serverOptions, serverOptions.logger ?? defaultProviderServerLogger);
2338
2527
  const options = {
2339
2528
  ...serverOptions,
2529
+ ...(localeCatalogs ? { localeCatalogs } : {}),
2340
2530
  capabilityModules,
2341
- engine: serverOptions.engine ?? createInProcessProviderEngine(),
2531
+ engine: engineSelection.engine,
2342
2532
  };
2533
+ const engineAttached = !isUnavailableProviderEngine(options.engine);
2343
2534
  const app = new Hono();
2344
2535
  // Compile the startup inventory once, composed with the shared outside-context fallback.
2345
2536
  const staticSensitiveValues = compileProcessDiagnosticSensitiveValues(collectStaticDiagnosticSensitiveValues(provider, {
@@ -2371,13 +2562,33 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2371
2562
  app.notFound((c) => {
2372
2563
  const error = new ProviderError("Not found", { code: "not_found", retryable: false });
2373
2564
  const observabilityDetails = errorObservabilityDetails(error);
2374
- return responseWithErrorObservability(c.json(toErrorResponse(error, undefined, observabilityDetails), 404), observabilityDetails);
2565
+ return responseWithErrorObservability(c.json(toErrorResponse(error, undefined, observabilityDetails, undefined, errorEnvelopeLocalization({
2566
+ catalogs: localeCatalogs,
2567
+ requestHeaders: c.req.raw.headers,
2568
+ })), 404), observabilityDetails);
2375
2569
  });
2570
+ // Liveness. Deliberately static and engine-blind: generated manifests point
2571
+ // liveness at this route, so making it depend on engine reachability would
2572
+ // restart every provider pod on one engine incident. Engine state belongs to
2573
+ // /readyz, which manifests wire to readiness and startup only.
2376
2574
  app.get("/health", (c) => c.json({
2377
2575
  status: "ok",
2378
2576
  provider: provider.id,
2379
2577
  version: provider.version,
2380
2578
  }));
2579
+ // Readiness. Reports the boot-resolved attachment; does not probe the engine
2580
+ // per call, so it cannot amplify an engine incident into request load.
2581
+ app.get("/readyz", (c) => c.json({
2582
+ status: engineAttached ? "ok" : "unavailable",
2583
+ provider: provider.id,
2584
+ version: provider.version,
2585
+ engine: {
2586
+ // The resolution, not the raw engine `kind`: normalized, so this
2587
+ // body and the `provider_engine_mode` boot event never disagree.
2588
+ mode: engineSelection.resolution.mode,
2589
+ attached: engineAttached,
2590
+ },
2591
+ }, engineAttached ? 200 : 503));
2381
2592
  app.post(STATEFUL_INTERNAL_OPERATIONS_ROUTE, async (c) => {
2382
2593
  let rawBodyText = "";
2383
2594
  let rawBody;
@@ -2483,7 +2694,10 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2483
2694
  requestId: request.requestId,
2484
2695
  operationId,
2485
2696
  headers: request.headers ?? {},
2486
- correlation: { connectionId: envelope.connectionId },
2697
+ correlation: {
2698
+ connectionId: envelope.connectionId,
2699
+ tenantId: resolveOperationTenantId(request),
2700
+ },
2487
2701
  });
2488
2702
  const response = await requestScope.run(async () => {
2489
2703
  const ctx = createProviderContext(provider, request, operationId, options, state, requestScope, signal);
@@ -2518,7 +2732,12 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2518
2732
  ...(requestId ? { requestId } : {}),
2519
2733
  });
2520
2734
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
2521
- const response = c.json(toErrorResponse(error, requestId, observabilityDetails, requestScope.redact), status);
2735
+ const response = c.json(toErrorResponse(error, requestId, observabilityDetails, requestScope.redact, errorEnvelopeLocalization({
2736
+ catalogs: localeCatalogs,
2737
+ requestHeaders: c.req.raw.headers,
2738
+ rawBody,
2739
+ ...(declaredErrorCode ? { declaredErrorCode } : {}),
2740
+ })), status);
2522
2741
  return finalizeRequestResponse(requestScope, response, {
2523
2742
  kind: "failed",
2524
2743
  status,
@@ -2552,7 +2771,10 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2552
2771
  requestScope.enrich({
2553
2772
  requestId: body.requestId,
2554
2773
  headers: body.headers,
2555
- correlation: { connectionId: resolveOperationConnectionId(body) },
2774
+ correlation: {
2775
+ connectionId: resolveOperationConnectionId(body),
2776
+ tenantId: resolveOperationTenantId(body),
2777
+ },
2556
2778
  });
2557
2779
  const streaming = provider.operations[operation]?.transport?.kind
2558
2780
  ? provider.operations[operation]?.transport?.kind !== "json"
@@ -2582,7 +2804,12 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2582
2804
  const requestId = extractRequestId(rawBody);
2583
2805
  requestScope.enrich({ ...(requestId ? { requestId } : {}) });
2584
2806
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
2585
- const response = c.json(toErrorResponse(error, requestId, observabilityDetails, requestScope.redact), status);
2807
+ const response = c.json(toErrorResponse(error, requestId, observabilityDetails, requestScope.redact, errorEnvelopeLocalization({
2808
+ catalogs: localeCatalogs,
2809
+ requestHeaders: c.req.raw.headers,
2810
+ rawBody,
2811
+ ...(declaredErrorCode ? { declaredErrorCode } : {}),
2812
+ })), status);
2586
2813
  return finalizeRequestResponse(requestScope, response, {
2587
2814
  kind: "failed",
2588
2815
  status,
@@ -2600,6 +2827,12 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2600
2827
  for (const { path, flowRoute, logRoute } of authRoutes) {
2601
2828
  app.post(path, async (c) => {
2602
2829
  let rawBody;
2830
+ // Negotiated once and shared by the success and failure paths so an
2831
+ // auth turn and an error envelope from the same request agree.
2832
+ let localization = errorEnvelopeLocalization({
2833
+ catalogs: localeCatalogs,
2834
+ requestHeaders: c.req.raw.headers,
2835
+ });
2603
2836
  const requestScope = createRequestScope({
2604
2837
  provider,
2605
2838
  staticSensitiveValues,
@@ -2614,6 +2847,11 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2614
2847
  .clone()
2615
2848
  .json()
2616
2849
  .catch(() => undefined);
2850
+ localization = errorEnvelopeLocalization({
2851
+ catalogs: localeCatalogs,
2852
+ requestHeaders: c.req.raw.headers,
2853
+ rawBody,
2854
+ });
2617
2855
  requestScope.seedCredentials(rawBody, "auth");
2618
2856
  const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
2619
2857
  requestScope.enrich({
@@ -2623,14 +2861,14 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2623
2861
  correlation: {
2624
2862
  connectionId: resolveOperationConnectionId(body),
2625
2863
  flowId: body.flowId,
2626
- tenantId: body.tenantId,
2864
+ tenantId: normalizeIdentifier(body.tenantId),
2627
2865
  requestedProviderId: body.providerId !== undefined && body.providerId !== provider.id
2628
2866
  ? body.providerId
2629
2867
  : undefined,
2630
2868
  },
2631
2869
  });
2632
2870
  const response = await requestScope.run(async () => {
2633
- const handled = await handleAuthFlow(provider, body, flowRoute, options, state, requestScope, c.req.raw.signal);
2871
+ const handled = await handleAuthFlow(provider, body, flowRoute, options, state, requestScope, localization.locale, c.req.raw.signal);
2634
2872
  return handled instanceof Response ? handled : c.json(handled);
2635
2873
  });
2636
2874
  return finalizeRequestResponse(requestScope, response, {
@@ -2643,7 +2881,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
2643
2881
  const requestId = extractRequestId(rawBody);
2644
2882
  requestScope.enrich({ ...(requestId ? { requestId } : {}) });
2645
2883
  const observabilityDetails = errorObservabilityDetails(error);
2646
- const response = c.json(toErrorResponse(error, requestId, observabilityDetails, requestScope.redact), status);
2884
+ const response = c.json(toErrorResponse(error, requestId, observabilityDetails, requestScope.redact, localization), status);
2647
2885
  return finalizeRequestResponse(requestScope, response, {
2648
2886
  kind: "failed",
2649
2887
  status,
@@ -2705,13 +2943,18 @@ export async function serve(provider, options = {}) {
2705
2943
  const configuredTimeoutMs = shutdownTimeout(options.shutdown?.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS);
2706
2944
  const configuredSignals = resolveShutdownSignals(options.shutdown?.signals ?? true);
2707
2945
  const selfTestSecrets = resolveSelfTestMasterSecrets();
2946
+ // Resolve the engine here so the boot event reports the truthful source
2947
+ // (option/env/default) and the app is handed the engine already selected —
2948
+ // one resolution per process, not one per app construction.
2949
+ const engineSelection = resolveServerEngine(options);
2708
2950
  const serverAppOptions = {
2709
2951
  logger: options.logger,
2710
- engine: options.engine,
2952
+ engine: engineSelection.engine,
2711
2953
  files: options.files,
2712
2954
  ocr: options.ocr,
2713
2955
  stt: options.stt,
2714
2956
  resolver: options.resolver,
2957
+ localeCatalogs: options.localeCatalogs,
2715
2958
  state: options.state,
2716
2959
  allowMemoryStateFallback: options.allowMemoryStateFallback,
2717
2960
  operationExecutor: options.operationExecutor,
@@ -2722,6 +2965,9 @@ export async function serve(provider, options = {}) {
2722
2965
  createServerAppAsync(provider, serverAppOptions),
2723
2966
  selfTestSecrets ? import("./self-test.js") : undefined,
2724
2967
  ]);
2968
+ // One structured line per process boot, before the listener binds: the fleet
2969
+ // audit (ADR-0011 migration) counts these.
2970
+ logger(engineModeLogEvent(provider, engineSelection.resolution, !isUnavailableProviderEngine(engineSelection.engine)));
2725
2971
  const servers = [];
2726
2972
  try {
2727
2973
  servers.push(bunRuntime.serve({