@apifuse/provider-sdk 2.2.0-beta.62 → 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 (243) hide show
  1. package/AUTHORING.md +268 -9
  2. package/CHANGELOG.md +4 -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 +187 -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/i18n/error-messages.d.ts +86 -0
  32. package/dist/i18n/error-messages.js +192 -0
  33. package/dist/i18n/index.d.ts +1 -0
  34. package/dist/i18n/index.js +1 -0
  35. package/dist/i18n/keys.d.ts +12 -0
  36. package/dist/i18n/keys.js +76 -5
  37. package/dist/index.d.ts +7 -1
  38. package/dist/index.js +6 -0
  39. package/dist/lint.d.ts +10 -0
  40. package/dist/lint.js +412 -49
  41. package/dist/provider.d.ts +5 -5
  42. package/dist/provider.js +2 -2
  43. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  44. package/dist/runtime/browser-telemetry-binding.js +83 -0
  45. package/dist/runtime/browser-telemetry.d.ts +76 -0
  46. package/dist/runtime/browser-telemetry.js +165 -0
  47. package/dist/runtime/browser.d.ts +3 -0
  48. package/dist/runtime/browser.js +208 -47
  49. package/dist/runtime/cache-telemetry.d.ts +71 -0
  50. package/dist/runtime/cache-telemetry.js +177 -0
  51. package/dist/runtime/cache.js +67 -7
  52. package/dist/runtime/capability-response.d.ts +3 -0
  53. package/dist/runtime/capability-response.js +56 -0
  54. package/dist/runtime/capability-telemetry.d.ts +46 -0
  55. package/dist/runtime/capability-telemetry.js +68 -0
  56. package/dist/runtime/diagnostic-env.d.ts +3 -0
  57. package/dist/runtime/diagnostic-env.js +10 -1
  58. package/dist/runtime/engine-mode.d.ts +74 -0
  59. package/dist/runtime/engine-mode.js +152 -0
  60. package/dist/runtime/http-telemetry-guard.d.ts +10 -0
  61. package/dist/runtime/http-telemetry-guard.js +62 -10
  62. package/dist/runtime/http.js +141 -35
  63. package/dist/runtime/instrumentation.js +92 -13
  64. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  65. package/dist/runtime/ocr-telemetry.js +192 -0
  66. package/dist/runtime/ocr.js +30 -8
  67. package/dist/runtime/request-telemetry.d.ts +2 -0
  68. package/dist/runtime/request-telemetry.js +2 -1
  69. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  70. package/dist/runtime/resolver-runtime-options.js +33 -0
  71. package/dist/runtime/resolver-shared.d.ts +3 -2
  72. package/dist/runtime/resolver-shared.js +4 -4
  73. package/dist/runtime/resolver-telemetry.d.ts +1 -0
  74. package/dist/runtime/resolver-telemetry.js +1 -1
  75. package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
  76. package/dist/runtime/resolver-vendors/browser.js +46 -0
  77. package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
  78. package/dist/runtime/resolver-vendors/hosts.js +35 -0
  79. package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
  80. package/dist/runtime/resolver-vendors/types.d.ts +5 -4
  81. package/dist/runtime/resolver.d.ts +4 -1
  82. package/dist/runtime/resolver.js +46 -13
  83. package/dist/runtime/state-telemetry.d.ts +79 -0
  84. package/dist/runtime/state-telemetry.js +194 -0
  85. package/dist/runtime/state.d.ts +3 -0
  86. package/dist/runtime/state.js +26 -7
  87. package/dist/runtime/stealth-cookies.d.ts +5 -1
  88. package/dist/runtime/stealth-cookies.js +21 -1
  89. package/dist/runtime/stealth-owned-headers.d.ts +26 -0
  90. package/dist/runtime/stealth-owned-headers.js +37 -0
  91. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  92. package/dist/runtime/stealth-telemetry.js +179 -0
  93. package/dist/runtime/stealth.d.ts +4 -1
  94. package/dist/runtime/stealth.js +228 -85
  95. package/dist/runtime/stt-telemetry.d.ts +77 -0
  96. package/dist/runtime/stt-telemetry.js +176 -0
  97. package/dist/runtime/stt.js +15 -6
  98. package/dist/server/error-stack-frames.d.ts +35 -0
  99. package/dist/server/error-stack-frames.js +219 -0
  100. package/dist/server/index.d.ts +7 -0
  101. package/dist/server/index.js +4 -0
  102. package/dist/server/sdk-version.d.ts +3 -0
  103. package/dist/server/sdk-version.js +19 -0
  104. package/dist/server/self-test.js +1 -18
  105. package/dist/server/sensitive-values.d.ts +1 -1
  106. package/dist/server/sensitive-values.js +9 -0
  107. package/dist/server/serve-implementation.d.ts +51 -2
  108. package/dist/server/serve-implementation.js +337 -90
  109. package/dist/server/types.d.ts +1 -0
  110. package/dist/server/types.js +7 -0
  111. package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
  112. package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
  113. package/dist/stateful/stateful-provider-runtime-context.js +1 -0
  114. package/dist/types.d.ts +94 -1
  115. package/package.json +6 -1
  116. package/src/cli/commands.ts +14 -0
  117. package/src/cli/deployment-intent.ts +364 -0
  118. package/src/cli/migrate-deployment-intent.ts +1908 -0
  119. package/src/cli/migrate-provider-shape.ts +10 -4
  120. package/src/cli/resolver-runtime.ts +72 -0
  121. package/src/define.ts +13 -0
  122. package/src/dev.ts +9 -2
  123. package/src/engine.ts +38 -2
  124. package/src/error-resolution.ts +58 -0
  125. package/src/errors.ts +77 -1
  126. package/src/i18n/error-messages.ts +255 -0
  127. package/src/i18n/index.ts +1 -0
  128. package/src/i18n/keys.ts +80 -6
  129. package/src/index.ts +72 -0
  130. package/src/lint.ts +522 -50
  131. package/src/provider.ts +10 -0
  132. package/src/runtime/browser-telemetry-binding.ts +92 -0
  133. package/src/runtime/browser-telemetry.ts +289 -0
  134. package/src/runtime/browser.ts +249 -65
  135. package/src/runtime/cache-telemetry.ts +247 -0
  136. package/src/runtime/cache.ts +68 -17
  137. package/src/runtime/capability-response.ts +53 -0
  138. package/src/runtime/capability-telemetry.ts +101 -0
  139. package/src/runtime/diagnostic-env.ts +11 -1
  140. package/src/runtime/engine-mode.ts +233 -0
  141. package/src/runtime/http-telemetry-guard.ts +66 -13
  142. package/src/runtime/http.ts +168 -37
  143. package/src/runtime/instrumentation.ts +105 -16
  144. package/src/runtime/ocr-telemetry.ts +308 -0
  145. package/src/runtime/ocr.ts +109 -74
  146. package/src/runtime/request-telemetry.ts +2 -1
  147. package/src/runtime/resolver-runtime-options.ts +50 -0
  148. package/src/runtime/resolver-shared.ts +9 -4
  149. package/src/runtime/resolver-telemetry.ts +1 -1
  150. package/src/runtime/resolver-vendors/browser.ts +65 -7
  151. package/src/runtime/resolver-vendors/hosts.ts +43 -0
  152. package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
  153. package/src/runtime/resolver-vendors/types.ts +5 -4
  154. package/src/runtime/resolver.ts +64 -21
  155. package/src/runtime/state-telemetry.ts +298 -0
  156. package/src/runtime/state.ts +46 -45
  157. package/src/runtime/stealth-cookies.ts +24 -1
  158. package/src/runtime/stealth-owned-headers.ts +41 -0
  159. package/src/runtime/stealth-telemetry.ts +304 -0
  160. package/src/runtime/stealth.ts +355 -150
  161. package/src/runtime/stt-telemetry.ts +282 -0
  162. package/src/runtime/stt.ts +93 -71
  163. package/src/server/error-stack-frames.ts +214 -0
  164. package/src/server/index.ts +53 -0
  165. package/src/server/sdk-version.ts +22 -0
  166. package/src/server/self-test.ts +1 -21
  167. package/src/server/sensitive-values.ts +14 -0
  168. package/src/server/serve-implementation.ts +569 -108
  169. package/src/server/types.ts +7 -0
  170. package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
  171. package/src/stateful/stateful-provider-runtime-context.ts +4 -0
  172. package/src/types.ts +99 -1
  173. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
  174. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
  175. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
  176. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
  177. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
  178. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
  179. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
  180. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
  181. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
  182. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
  183. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
  184. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
  185. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
  186. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
  187. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
  188. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
  189. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
  190. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
  191. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
  192. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
  193. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
  194. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
  195. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
  196. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
  197. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
  198. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
  199. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
  200. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
  201. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
  202. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
  203. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
  204. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
  205. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
  206. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
  207. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
  208. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
  209. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
  210. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
  211. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
  212. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
  213. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
  214. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
  215. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
  216. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
  217. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
  218. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
  219. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
  220. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
  221. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
  222. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
  223. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
  224. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
  225. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
  226. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
  227. package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
@@ -1,3 +1,4 @@
1
+ import { bindCapabilityRoot } from "../runtime/capability-telemetry.js";
1
2
  import { STATUS_CODES } from "node:http";
2
3
  import { readDiagnosticEnv, withDiagnosticEnv } from "../runtime/diagnostic-env.js";
3
4
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -11,14 +12,22 @@ import { z } from "zod";
11
12
  import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
12
13
  import { validateFailClosedDeclaration } from "../declaration-validation.js";
13
14
  import {
14
- createInProcessProviderEngine,
15
15
  ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES,
16
16
  isEngineOwnedEnvName,
17
17
  type ProviderEngine,
18
18
  type ProviderEngineBindingCandidates,
19
+ type ProviderEngineMode,
19
20
  readEngineProxyCredentials,
20
21
  } from "../engine.js";
22
+ import {
23
+ createEngineForMode,
24
+ isUnavailableProviderEngine,
25
+ type ProviderEngineModeResolution,
26
+ resolveProviderEngineMode,
27
+ } from "../runtime/engine-mode.js";
28
+ import { SDK_VERSION } from "./sdk-version.js";
21
29
  import { safeProviderErrorObservability } from "../error-observability.js";
30
+ import { providerErrorStackFrames } from "./error-stack-frames.js";
22
31
  import {
23
32
  SDK_OWNED_PROVIDER_ERROR_CODES,
24
33
  SDK_RUNTIME_OWNED_ERROR_CODES,
@@ -41,6 +50,15 @@ import {
41
50
  localizeAuthTurn,
42
51
  type ProviderLocaleCatalogMap,
43
52
  } from "../i18n/catalog.js";
53
+ import {
54
+ acceptLanguageHeaderValue,
55
+ DEFAULT_PROVIDER_ERROR_LOCALE,
56
+ localizeProviderErrorText,
57
+ PROVIDER_ERROR_LOCALES,
58
+ type ProviderErrorMessageParams,
59
+ type ProviderErrorTextCandidate,
60
+ resolveProviderErrorLocale,
61
+ } from "../i18n/error-messages.js";
44
62
  import type { ProviderLocale } from "../i18n/keys.js";
45
63
  import {
46
64
  categoryForStatus,
@@ -53,6 +71,7 @@ import {
53
71
  import { createScratchpad } from "../runtime/auth-flow.js";
54
72
  import type * as BrowserRuntimeModule from "../runtime/browser.js";
55
73
  import { createProviderCache } from "../runtime/cache.js";
74
+ import { bindCacheTelemetry, CacheTelemetryCollector } from "../runtime/cache-telemetry.js";
56
75
  import type { HandleTelemetryEvent } from "../handle.js";
57
76
  import { createHandleContext } from "../runtime/handle.js";
58
77
  import { createCredentialContext } from "../runtime/credential.js";
@@ -86,6 +105,7 @@ import { HttpTelemetryCollector, type HttpTelemetryLogPayload } from "../runtime
86
105
  import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
87
106
  import type * as NativeNetworkRuntimeModule from "../runtime/native-network.js";
88
107
  import { createOcrClientFromEnv } from "../runtime/ocr.js";
108
+ import { bindOcrTelemetry, OcrTelemetryCollector } from "../runtime/ocr-telemetry.js";
89
109
  import { getProviderBaseUrl } from "../runtime/provider.js";
90
110
  import {
91
111
  PROXY_AUTH_IP_DENIED_CODE,
@@ -116,6 +136,10 @@ import {
116
136
  ResolverTelemetryCollector,
117
137
  type ResolverTelemetryLogPayload,
118
138
  } from "../runtime/resolver-telemetry.js";
139
+ import {
140
+ createResolverRuntimeOptions,
141
+ resolveNativeProxyPolicy,
142
+ } from "../runtime/resolver-runtime-options.js";
119
143
  import {
120
144
  assertRequiredSecretsPresent,
121
145
  listMissingRequiredSecrets,
@@ -125,10 +149,22 @@ import {
125
149
  createProviderRuntimeStateFromEnv,
126
150
  createUnsupportedProviderRuntimeState,
127
151
  } from "../runtime/state.js";
152
+ import {
153
+ StateTelemetryCollector,
154
+ instrumentProviderRuntimeState,
155
+ } from "../runtime/state-telemetry.js";
128
156
  import type * as StealthRuntimeModule from "../runtime/stealth.js";
129
157
  import type { StealthChallengeRuntime } from "../runtime/stealth-akamai-sbsd.js";
130
158
  import { StealthCookieJar } from "../runtime/stealth-cookies.js";
159
+ import { StealthTelemetryCollector } from "../runtime/stealth-telemetry.js";
160
+ import { BrowserTelemetryCollector } from "../runtime/browser-telemetry.js";
161
+ import {
162
+ bindBrowserTelemetry,
163
+ isSupportedBrowserBinding,
164
+ warnUnsupportedBrowserBinding,
165
+ } from "../runtime/browser-telemetry-binding.js";
131
166
  import { createSttClientFromEnv } from "../runtime/stt.js";
167
+ import { bindSttTelemetry, SttTelemetryCollector } from "../runtime/stt-telemetry.js";
132
168
  import {
133
169
  createTraceContext,
134
170
  getTraceRecorder,
@@ -169,8 +205,8 @@ import type {
169
205
  ProviderDefinition,
170
206
  ProviderErrorStatus,
171
207
  ProviderFilesContext,
172
- ProviderProxyPolicy,
173
208
  ProviderRuntimeState,
209
+ ProviderRuntimeTarget,
174
210
  ProviderStreamEvent,
175
211
  ResolverContext,
176
212
  StealthClient,
@@ -238,7 +274,6 @@ function providerErrorCode(error: unknown): string | undefined {
238
274
  }
239
275
  }
240
276
 
241
- const AUTH_FLOW_LOCALES = ["en", "ko", "ja"] as const;
242
277
  const retryResponseMeta = new WeakMap<ProviderContext, HttpTelemetryCollector>();
243
278
  const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
244
279
  const STATEFUL_FORWARDING_SOURCE_POD_HEADER = "x-apifuse-stateful-source-pod";
@@ -272,6 +307,22 @@ export const ProviderServerStatefulForwardEnvelopeSchema = z
272
307
  })
273
308
  .strict();
274
309
 
310
+ // Inbound compatibility boundary for rolling deploys, mirroring the outbound
311
+ // one in stateful-provider-owner-forwarder.ts: emitted envelopes stay strict
312
+ // (above), while a received envelope's nested operation request keeps the
313
+ // gateway route's tolerance and strips unknown keys. A source pod on a newer
314
+ // SDK carrying an additive OperationRequestSchema field (tenantId, #164) must
315
+ // not be rejected with STATEFUL_FORWARDING_ENVELOPE_INVALID by an owner pod
316
+ // that predates the field: the rejection is terminal for the operation and the
317
+ // two pods coexist for the whole rolling update. The envelope's own keys stay
318
+ // strict on both sides, because they are protocol rather than payload.
319
+ const ReceivedStatefulForwardEnvelopeSchema =
320
+ ProviderServerStatefulForwardEnvelopeSchema.extend({
321
+ operationRequest: z.object(
322
+ ProviderServerStatefulForwardEnvelopeSchema.shape.operationRequest.shape,
323
+ ),
324
+ });
325
+
275
326
  export type ProviderServerStatefulForwardEnvelope = Readonly<
276
327
  z.infer<typeof ProviderServerStatefulForwardEnvelopeSchema>
277
328
  >;
@@ -599,36 +650,6 @@ function getProviderStealthProfile(provider: ProviderDefinition) {
599
650
 
600
651
  type ResolverRuntimeOptions = ResolverRuntimeModule.ResolverRuntimeOptions;
601
652
 
602
- /** One option set for ctx.resolver and the automatic SBSD solve, so both share identity scope, cache, proxy intent, and telemetry. */
603
- function createResolverRuntimeOptions(
604
- provider: ProviderDefinition,
605
- cache: ReturnType<typeof createProviderCache>,
606
- identityScope: string,
607
- proxyPolicy: ProviderProxyPolicy | undefined,
608
- proxyClientOptions: Omit<
609
- NonNullable<ResolverRuntimeOptions["proxyIntent"]>,
610
- "mode" | "userAgent"
611
- >,
612
- stealthProfile: { readonly userAgent: string } | undefined,
613
- telemetry: ResolverTelemetryCollector,
614
- ): ResolverRuntimeOptions {
615
- return {
616
- allowedHosts: provider.allowedHosts,
617
- cache,
618
- telemetry,
619
- identityScope,
620
- ...(proxyPolicy
621
- ? {
622
- proxyIntent: {
623
- mode: proxyPolicy.mode,
624
- ...proxyClientOptions,
625
- ...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
626
- },
627
- }
628
- : {}),
629
- };
630
- }
631
-
632
653
  /** Shared predicate for the SBSD challenge runtime and the ceremony egress lease. */
633
654
  function declaresAkamaiSbsdResolver(provider: ProviderDefinition): boolean {
634
655
  return provider.resolver?.kinds.some((kind) => kind === "akamai_sbsd") === true;
@@ -660,12 +681,17 @@ function createStealthChallengeDetection(
660
681
  async solve(challenge, transport, solveSignal) {
661
682
  const resolver = resolverRuntime.bindResolverSignal(
662
683
  (resolverOverride
663
- ? bindResolverTelemetry(resolverOverride, resolverOptions.telemetry)
684
+ ? bindResolverTelemetry(
685
+ resolverOverride,
686
+ resolverOptions.telemetry,
687
+ resolverOptions.browserTelemetry,
688
+ )
664
689
  : undefined) ??
665
690
  resolverRuntime.createResolverClientFromEnv(provider.resolver, undefined, {
666
691
  allowedHosts: resolverOptions.allowedHosts,
667
692
  cache: resolverOptions.cache,
668
693
  telemetry: resolverOptions.telemetry,
694
+ browserTelemetry: resolverOptions.browserTelemetry,
669
695
  identityScope: resolverOptions.identityScope,
670
696
  // No proxyIntent: the transport is already bound to the initiating
671
697
  // request's lease, and a second identity resolution would allocate
@@ -800,20 +826,18 @@ function resolveOperationConnectionId(
800
826
  // absent so it can never override a valid id or key a real scope. Requests
801
827
  // without any usable id fall back to the documented missing-connection
802
828
  // sentinel scope instead of scoping context/affinity/state under "".
803
- return (
804
- normalizeConnectionId(request.connection?.id) ?? normalizeConnectionId(request.connectionId)
805
- );
829
+ return normalizeIdentifier(request.connection?.id) ?? normalizeIdentifier(request.connectionId);
806
830
  }
807
831
 
808
- function normalizeConnectionId(id: string | undefined): string | undefined {
809
- return id === "" ? undefined : id;
832
+ function resolveOperationTenantId(request: Pick<OperationRequest, "tenantId">): string | undefined {
833
+ // Only the gateway-asserted envelope field is consulted; caller headers can
834
+ // never populate the principal scope. Same rule as connection ids: "" is a
835
+ // malformed identifier, not a principal.
836
+ return normalizeIdentifier(request.tenantId);
810
837
  }
811
838
 
812
- function resolveNativeProxyPolicy(provider: ProviderDefinition): ProviderProxyPolicy | undefined {
813
- if (typeof provider.proxy === "object") return provider.proxy;
814
- if (provider.proxy === true) return { mode: "optional" };
815
- if (provider.proxy === false) return { mode: "disabled" };
816
- return undefined;
839
+ function normalizeIdentifier(id: string | undefined): string | undefined {
840
+ return id === "" ? undefined : id;
817
841
  }
818
842
 
819
843
  function providerSecretNames(provider: ProviderDefinition): string[] {
@@ -823,12 +847,19 @@ function providerSecretNames(provider: ProviderDefinition): string[] {
823
847
  }
824
848
 
825
849
  type RequestScopeContext = {
850
+ run<T>(fn: () => Promise<T>): Promise<T>;
826
851
  redact: DiagnosticRedactor;
827
852
  trace: RuntimeTraceContext;
828
853
  telemetry: RequestTelemetry;
829
854
  resolverTelemetry: ResolverTelemetryCollector;
830
855
  nativeTelemetry: NativeTelemetryCollector;
831
856
  httpTelemetry: HttpTelemetryCollector;
857
+ stealthTelemetry: StealthTelemetryCollector;
858
+ ocrTelemetry: OcrTelemetryCollector;
859
+ sttTelemetry: SttTelemetryCollector;
860
+ browserTelemetry: BrowserTelemetryCollector;
861
+ cacheTelemetry: CacheTelemetryCollector;
862
+ stateTelemetry: StateTelemetryCollector;
832
863
  };
833
864
 
834
865
  function createProviderContext(
@@ -856,7 +887,9 @@ function createProviderContext(
856
887
  proxyClientOptions.affinityKey,
857
888
  request.requestId,
858
889
  );
859
- const cache = createProviderCache({ providerId: provider.id });
890
+ const cache = createProviderCache(
891
+ bindCacheTelemetry({ providerId: provider.id }, scope.cacheTelemetry),
892
+ );
860
893
  const { capabilityModules } = options;
861
894
  const resolverOptions = createResolverRuntimeOptions(
862
895
  provider,
@@ -866,6 +899,7 @@ function createProviderContext(
866
899
  proxyClientOptions,
867
900
  stealthProfile,
868
901
  scope.resolverTelemetry,
902
+ scope.browserTelemetry,
869
903
  );
870
904
  const challengeRuntime = createStealthChallengeDetection(
871
905
  provider,
@@ -876,6 +910,7 @@ function createProviderContext(
876
910
  signal,
877
911
  );
878
912
  const stealthClientOptions = {
913
+ stealthTelemetry: scope.stealthTelemetry,
879
914
  upstream: proxyClientOptions.upstream,
880
915
  affinityKey: proxyClientOptions.affinityKey,
881
916
  telemetry: scope.telemetry.proxy,
@@ -911,11 +946,19 @@ function createProviderContext(
911
946
  scopes: request.connection?.scopes,
912
947
  values: request.connection?.secrets,
913
948
  });
949
+ const operationTenantId = resolveOperationTenantId(request);
914
950
  const requestContext = {
915
951
  connectionId: resolveOperationConnectionId(request),
952
+ // Absent stays absent: an own property with an undefined value would
953
+ // change the shape of ctx.request for every existing consumer.
954
+ ...(operationTenantId !== undefined ? { tenantId: operationTenantId } : {}),
916
955
  headers: request.headers ?? {},
917
956
  };
918
- const requestState = state.forConnection(requestContext.connectionId);
957
+ const requestState = instrumentProviderRuntimeState(
958
+ state,
959
+ scope.stateTelemetry,
960
+ scope.redact,
961
+ ).forConnection(requestContext.connectionId);
919
962
  const bindings: ProviderEngineBindingCandidates = {
920
963
  env,
921
964
  credential,
@@ -942,6 +985,7 @@ function createProviderContext(
942
985
  requireCdpPool: isProductionProviderBrowserMode(provider),
943
986
  stealth: true,
944
987
  engine: provider.browser?.engine,
988
+ telemetry: scope.browserTelemetry,
945
989
  })
946
990
  : createBrowserStub(),
947
991
  ...(provider.native
@@ -962,12 +1006,22 @@ function createProviderContext(
962
1006
  : {}),
963
1007
  trace: scope.trace,
964
1008
  auth: createAuthStub(),
965
- ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
966
- stt: options.stt ?? createSttClientFromEnv(provider.stt),
1009
+ ocr: bindCapabilityRoot(
1010
+ bindOcrTelemetry(options.ocr ?? createOcrClientFromEnv(provider.ocr), scope.ocrTelemetry),
1011
+ scope.run,
1012
+ ),
1013
+ stt: bindCapabilityRoot(
1014
+ bindSttTelemetry(options.stt ?? createSttClientFromEnv(provider.stt), scope.sttTelemetry),
1015
+ scope.run,
1016
+ ),
967
1017
  resolver: capabilityModules.resolver
968
1018
  ? capabilityModules.resolver.bindResolverSignal(
969
1019
  (options.resolver
970
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
1020
+ ? bindResolverTelemetry(
1021
+ options.resolver,
1022
+ resolverOptions.telemetry,
1023
+ resolverOptions.browserTelemetry,
1024
+ )
971
1025
  : undefined) ??
972
1026
  capabilityModules.resolver.createResolverClientFromEnv(
973
1027
  provider.resolver,
@@ -978,7 +1032,11 @@ function createProviderContext(
978
1032
  )
979
1033
  : bindResolverSignalWithoutRuntime(
980
1034
  (options.resolver
981
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
1035
+ ? bindResolverTelemetry(
1036
+ options.resolver,
1037
+ resolverOptions.telemetry,
1038
+ resolverOptions.browserTelemetry,
1039
+ )
982
1040
  : undefined) ??
983
1041
  createUnsupportedResolverClient("Provider does not declare resolver capability"),
984
1042
  signal,
@@ -996,6 +1054,7 @@ function createProviderContext(
996
1054
  }),
997
1055
  };
998
1056
  const candidateHttp = bindings.http;
1057
+ const candidateBrowser = bindings.browser;
999
1058
  const attached = options.engine.attach({ provider, bindings }) as ProviderContext;
1000
1059
  const hostHttp = provider.http ? attached.http : undefined;
1001
1060
  const supportedHostHttp = provider.http && isSupportedHttpBinding(hostHttp);
@@ -1004,19 +1063,31 @@ function createProviderContext(
1004
1063
  supportedHostHttp && hostHttp !== candidateHttp
1005
1064
  ? bindHttpTelemetry(hostHttp, scope.httpTelemetry)
1006
1065
  : undefined;
1007
- const contextTarget = observedHttp
1008
- ? new Proxy(attached, {
1009
- get(target, property, receiver) {
1010
- return property === "http" ? observedHttp : Reflect.get(target, property, receiver);
1011
- },
1012
- })
1013
- : provider.http && !supportedHostHttp
1066
+ const hostBrowser = provider.runtime === "browser" ? attached.browser : undefined;
1067
+ const supportedHostBrowser =
1068
+ provider.runtime === "browser" && isSupportedBrowserBinding(hostBrowser);
1069
+ if (provider.runtime === "browser" && !supportedHostBrowser) warnUnsupportedBrowserBinding();
1070
+ const observedBrowser =
1071
+ supportedHostBrowser && hostBrowser !== candidateBrowser
1072
+ ? bindBrowserTelemetry(hostBrowser, scope.browserTelemetry)
1073
+ : undefined;
1074
+ const contextTarget =
1075
+ observedBrowser || observedHttp
1014
1076
  ? new Proxy(attached, {
1015
1077
  get(target, property, receiver) {
1016
- return property === "http" ? undefined : Reflect.get(target, property, receiver);
1078
+ if (property === "http" && observedHttp) return observedHttp;
1079
+ if (property === "http" && provider.http && !supportedHostHttp) return undefined;
1080
+ if (property === "browser" && observedBrowser) return observedBrowser;
1081
+ return Reflect.get(target, property, receiver);
1017
1082
  },
1018
1083
  })
1019
- : attached;
1084
+ : provider.http && !supportedHostHttp
1085
+ ? new Proxy(attached, {
1086
+ get(target, property, receiver) {
1087
+ return property === "http" ? undefined : Reflect.get(target, property, receiver);
1088
+ },
1089
+ })
1090
+ : attached;
1020
1091
  const context = wrapWithInstrumentation(contextTarget);
1021
1092
  retryResponseMeta.set(context, scope.httpTelemetry);
1022
1093
  return context;
@@ -1133,7 +1204,9 @@ function createAuthFlowContext(
1133
1204
  proxyClientOptions.affinityKey,
1134
1205
  request.requestId,
1135
1206
  );
1136
- const cache = createProviderCache({ providerId: provider.id });
1207
+ const cache = createProviderCache(
1208
+ bindCacheTelemetry({ providerId: provider.id }, scope.cacheTelemetry),
1209
+ );
1137
1210
  const { capabilityModules } = options;
1138
1211
  const resolverOptions = createResolverRuntimeOptions(
1139
1212
  provider,
@@ -1143,6 +1216,7 @@ function createAuthFlowContext(
1143
1216
  proxyClientOptions,
1144
1217
  stealthProfile,
1145
1218
  scope.resolverTelemetry,
1219
+ scope.browserTelemetry,
1146
1220
  );
1147
1221
  const challengeRuntime = createStealthChallengeDetection(
1148
1222
  provider,
@@ -1153,6 +1227,7 @@ function createAuthFlowContext(
1153
1227
  signal,
1154
1228
  );
1155
1229
  const stealthClientOptions = {
1230
+ stealthTelemetry: scope.stealthTelemetry,
1156
1231
  upstream: proxyClientOptions.upstream,
1157
1232
  affinityKey: proxyClientOptions.affinityKey,
1158
1233
  telemetry: scope.telemetry.proxy,
@@ -1201,12 +1276,28 @@ function createAuthFlowContext(
1201
1276
  ...(signal ? { signal } : {}),
1202
1277
  httpTelemetry: scope.httpTelemetry,
1203
1278
  }),
1204
- state: state.forConnection(resolveOperationConnectionId(request)),
1279
+ state: instrumentProviderRuntimeState(
1280
+ state,
1281
+ scope.stateTelemetry,
1282
+ scope.redact,
1283
+ ).forConnection(resolveOperationConnectionId(request)),
1205
1284
  stealth: stealthBaseUrl
1206
1285
  ? capabilityModules.stealth
1207
1286
  ? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
1208
1287
  : createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
1209
1288
  : createStealthStub(),
1289
+ browser:
1290
+ provider.runtime === "browser"
1291
+ ? capabilityModules.browser!.createBrowserClient({
1292
+ allowedHosts: provider.allowedHosts,
1293
+ cdpUrl: readDiagnosticEnv("APIFUSE__CDP_POOL__URL"),
1294
+ headless: true,
1295
+ requireCdpPool: isProductionProviderBrowserMode(provider),
1296
+ stealth: true,
1297
+ engine: provider.browser?.engine,
1298
+ telemetry: scope.browserTelemetry,
1299
+ })
1300
+ : createBrowserStub(),
1210
1301
  ...(provider.native
1211
1302
  ? {
1212
1303
  native: {
@@ -1229,12 +1320,22 @@ function createAuthFlowContext(
1229
1320
  ]),
1230
1321
  credential,
1231
1322
  context: flowContextStore.context,
1232
- ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
1233
- stt: options.stt ?? createSttClientFromEnv(provider.stt),
1323
+ ocr: bindCapabilityRoot(
1324
+ bindOcrTelemetry(options.ocr ?? createOcrClientFromEnv(provider.ocr), scope.ocrTelemetry),
1325
+ scope.run,
1326
+ ),
1327
+ stt: bindCapabilityRoot(
1328
+ bindSttTelemetry(options.stt ?? createSttClientFromEnv(provider.stt), scope.sttTelemetry),
1329
+ scope.run,
1330
+ ),
1234
1331
  resolver: capabilityModules.resolver
1235
1332
  ? capabilityModules.resolver.bindResolverSignal(
1236
1333
  (options.resolver
1237
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
1334
+ ? bindResolverTelemetry(
1335
+ options.resolver,
1336
+ resolverOptions.telemetry,
1337
+ resolverOptions.browserTelemetry,
1338
+ )
1238
1339
  : undefined) ??
1239
1340
  capabilityModules.resolver.createResolverClientFromEnv(
1240
1341
  provider.resolver,
@@ -1245,7 +1346,11 @@ function createAuthFlowContext(
1245
1346
  )
1246
1347
  : bindResolverSignalWithoutRuntime(
1247
1348
  (options.resolver
1248
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
1349
+ ? bindResolverTelemetry(
1350
+ options.resolver,
1351
+ resolverOptions.telemetry,
1352
+ resolverOptions.browserTelemetry,
1353
+ )
1249
1354
  : undefined) ??
1250
1355
  createUnsupportedResolverClient("Provider does not declare resolver capability"),
1251
1356
  signal,
@@ -1309,6 +1414,7 @@ export type ProviderServerLogEvent =
1309
1414
  retryable?: boolean;
1310
1415
  providerObservability?: ProviderErrorObservability;
1311
1416
  causeChain?: ProviderErrorCauseFrame[];
1417
+ stack?: string[];
1312
1418
  signal?: "unregistered_provider_error_code";
1313
1419
  signalFix?: string;
1314
1420
  issues?: Array<{ path: string; code: string; message: string }>;
@@ -1319,6 +1425,13 @@ export type ProviderServerLogEvent =
1319
1425
  providerId: string;
1320
1426
  missingSecrets: string[];
1321
1427
  }
1428
+ | {
1429
+ /** Emitted once at boot when `locales/en.json` exists but cannot be read. */
1430
+ level: "warn";
1431
+ event: "provider_locale_catalogs_unavailable";
1432
+ providerId: string;
1433
+ reason: string;
1434
+ }
1322
1435
  | ({
1323
1436
  level: "info";
1324
1437
  event: "provider_handle";
@@ -1342,6 +1455,27 @@ export type ProviderServerLogEvent =
1342
1455
  errorClass: string;
1343
1456
  message: string;
1344
1457
  }
1458
+ | {
1459
+ /** `warn` when the requested attachment could not be honoured, otherwise `info`. */
1460
+ level: "info" | "warn";
1461
+ event: "provider_engine_mode";
1462
+ providerId: string;
1463
+ /** Resolved attachment; `custom` for an opaque host engine. */
1464
+ mode: ProviderEngineMode | "custom";
1465
+ source: "engine" | "env" | "option" | "default";
1466
+ /** True for every non-remote attachment, whose removal is scheduled by ADR-0011. */
1467
+ deprecated: boolean;
1468
+ /** False when the engine refuses to attach; `/readyz` reports 503. */
1469
+ attached: boolean;
1470
+ sdkVersion: string;
1471
+ runtimeTarget?: ProviderRuntimeTarget;
1472
+ /**
1473
+ * Non-fatal selection findings, e.g. `invalid_env_value`,
1474
+ * `env_overrode_option`, `engine_object_overrode_requested_mode`,
1475
+ * `engine_env_ignored`. Absent when the selection was unambiguous.
1476
+ */
1477
+ warnings?: readonly string[];
1478
+ }
1345
1479
  | SelfTestCancellationLogEvent;
1346
1480
 
1347
1481
  export type ProviderServerLogger = (event: ProviderServerLogEvent) => void;
@@ -1350,6 +1484,17 @@ export type ProviderServerOptions<TContext extends Partial<ProviderContext> = Pr
1350
1484
  logger?: ProviderServerLogger;
1351
1485
  /** Capability attachment boundary. Defaults to the local in-process engine. */
1352
1486
  engine?: ProviderEngine;
1487
+ /**
1488
+ * Explicit engine attachment mode. Lowest precedence of the three inputs:
1489
+ * an explicit `engine` object wins, then `APIFUSE__ENGINE__MODE` (the
1490
+ * deployment outranks provider code), then this option, then the in-process
1491
+ * default. Disagreement is never fatal — it is reported on the
1492
+ * `provider_engine_mode` boot event (`env_overrode_option`,
1493
+ * `engine_object_overrode_requested_mode`), which only `serve` emits. A value
1494
+ * that is not a known mode throws `ValidationError`, because unlike the env it
1495
+ * can only come from calling code.
1496
+ */
1497
+ engineMode?: ProviderEngineMode;
1353
1498
  /** Request-file resolver supplied by the engine host when `files` is declared. */
1354
1499
  files?: ProviderFilesContext;
1355
1500
  /** Optional provider-specific operation executor. Stateful providers use this to preserve provider-local runtime semantics. */
@@ -1373,6 +1518,15 @@ export type ProviderServerOptions<TContext extends Partial<ProviderContext> = Pr
1373
1518
  * resolver is reported as one custom invocation; its internal vendor work is not exposed.
1374
1519
  */
1375
1520
  resolver?: ResolverContext;
1521
+ /**
1522
+ * Pre-loaded provider locale catalogs used to localize auth turns and the
1523
+ * client-facing `message`/`fix` of provider errors.
1524
+ *
1525
+ * Defaults to reading `locales/{en,ko,ja}.json` from the provider directory
1526
+ * once at server construction. Supply this to bundle catalogs instead of
1527
+ * probing the filesystem, or to pin catalogs in tests.
1528
+ */
1529
+ localeCatalogs?: ProviderLocaleCatalogMap;
1376
1530
  /** Optional runtime state override for tests or custom hosts. Production resolves Redis from env and fails closed when unavailable. */
1377
1531
  state?: ProviderRuntimeState;
1378
1532
  /** Allow process-local runtime state only for local development and tests. */
@@ -1467,11 +1621,116 @@ function publicErrorSource(error: unknown, category: ProviderErrorCategory): Pro
1467
1621
  return sourceForCategory(category);
1468
1622
  }
1469
1623
 
1624
+ /**
1625
+ * Serve-time inputs for localizing the client-facing error envelope.
1626
+ *
1627
+ * Only `error.message` and `error.fix` of the response body are localized.
1628
+ * Logs, cause frames, OTLP attributes and the error-observability header keep
1629
+ * the English literal the provider threw.
1630
+ */
1631
+ type ErrorEnvelopeLocalization = {
1632
+ readonly catalogs?: ProviderLocaleCatalogMap;
1633
+ readonly locale: ProviderLocale;
1634
+ readonly declaredErrorCode?: OperationErrorCode;
1635
+ };
1636
+
1637
+ function localeKeyOption(error: unknown, key: "messageKey" | "fixKey"): string | undefined {
1638
+ const value = providerErrorOption(error, key);
1639
+ return typeof value === "string" ? value : undefined;
1640
+ }
1641
+
1642
+ function declaredLocaleKey(
1643
+ declaration: OperationErrorCode | undefined,
1644
+ key: "messageKey" | "fixKey",
1645
+ ): string | undefined {
1646
+ const value = declaration?.[key];
1647
+ return typeof value === "string" ? value : undefined;
1648
+ }
1649
+
1650
+ function providerErrorParams(error: unknown): ProviderErrorMessageParams | undefined {
1651
+ const params = providerErrorOption(error, "params");
1652
+ return params !== null && typeof params === "object" && !Array.isArray(params)
1653
+ ? (params as ProviderErrorMessageParams)
1654
+ : undefined;
1655
+ }
1656
+
1657
+ /**
1658
+ * Resolution order for one localized envelope field:
1659
+ *
1660
+ * 1. the throw site's `messageKey`/`fixKey`;
1661
+ * 2. the declared `errorCodes[]` entry's key for the thrown code;
1662
+ * 3. the derived `errors.<code>.<field>` convention;
1663
+ * 4. the English literal the provider threw.
1664
+ *
1665
+ * Steps 2 and 3 are skipped for SDK-owned failures (transport, Zod, stateful
1666
+ * deadline, SDK runtime codes): their text is the SDK's contract, and a
1667
+ * provider catalog entry must not be able to relabel one. An explicit key on
1668
+ * the thrown error still wins, because that is the provider deliberately
1669
+ * describing an error it constructed itself.
1670
+ */
1671
+ function localizedErrorField(
1672
+ error: unknown,
1673
+ field: "message" | "fix",
1674
+ literal: string | undefined,
1675
+ localization: ErrorEnvelopeLocalization | undefined,
1676
+ ): string | undefined {
1677
+ if (!localization?.catalogs) return literal;
1678
+ const optionKey = field === "message" ? "messageKey" : "fixKey";
1679
+ const candidates: ProviderErrorTextCandidate[] = [
1680
+ { kind: "key", key: localeKeyOption(error, optionKey) },
1681
+ ];
1682
+ if (!sdkOwnsErrorResolution(error)) {
1683
+ candidates.push(
1684
+ { kind: "key", key: declaredLocaleKey(localization.declaredErrorCode, optionKey) },
1685
+ { kind: "derived", code: providerErrorCode(error), field },
1686
+ );
1687
+ }
1688
+ return localizeProviderErrorText({
1689
+ catalogs: localization.catalogs,
1690
+ locale: localization.locale,
1691
+ candidates,
1692
+ params: providerErrorParams(error),
1693
+ fallback: literal,
1694
+ });
1695
+ }
1696
+
1697
+ /**
1698
+ * Reads `accept-language` out of the request envelope's `headers` map.
1699
+ *
1700
+ * The gateway stamps the negotiated locale there for auth flows (it persists
1701
+ * the start locale on the flow state so continue/poll turns stay in one
1702
+ * language), and the operation route merges HTTP headers into the same map, so
1703
+ * the envelope is the higher-precedence source.
1704
+ */
1705
+ function requestBodyAcceptLanguage(rawBody: unknown): string | undefined {
1706
+ if (!rawBody || typeof rawBody !== "object") return undefined;
1707
+ const headers = Object.getOwnPropertyDescriptor(rawBody, "headers")?.value;
1708
+ if (!headers || typeof headers !== "object" || Array.isArray(headers)) return undefined;
1709
+ return acceptLanguageHeaderValue(headers as Record<string, unknown>);
1710
+ }
1711
+
1712
+ function errorEnvelopeLocalization(options: {
1713
+ readonly catalogs: ProviderLocaleCatalogMap | undefined;
1714
+ readonly requestHeaders?: Headers;
1715
+ readonly rawBody?: unknown;
1716
+ readonly declaredErrorCode?: OperationErrorCode;
1717
+ }): ErrorEnvelopeLocalization {
1718
+ return {
1719
+ ...(options.catalogs ? { catalogs: options.catalogs } : {}),
1720
+ locale: resolveProviderErrorLocale(
1721
+ requestBodyAcceptLanguage(options.rawBody),
1722
+ options.requestHeaders?.get("accept-language"),
1723
+ ),
1724
+ ...(options.declaredErrorCode ? { declaredErrorCode: options.declaredErrorCode } : {}),
1725
+ };
1726
+ }
1727
+
1470
1728
  function toErrorResponse(
1471
1729
  error: unknown,
1472
1730
  requestId: string | undefined,
1473
1731
  observabilityDetails: ErrorObservabilityDetails,
1474
1732
  redact?: DiagnosticRedactor,
1733
+ localization?: ErrorEnvelopeLocalization,
1475
1734
  ): OperationErrorResponse {
1476
1735
  const observability = observabilityDetails;
1477
1736
  const source = publicErrorSource(error, observability.category);
@@ -1489,14 +1748,23 @@ function toErrorResponse(
1489
1748
 
1490
1749
  if (isProviderError(error)) {
1491
1750
  const details = providerErrorOption(error, "details");
1751
+ const literalFix = providerErrorOption(error, "fix");
1752
+ const fix = localizedErrorField(
1753
+ error,
1754
+ "fix",
1755
+ typeof literalFix === "string" && literalFix.length > 0 ? literalFix : undefined,
1756
+ localization,
1757
+ );
1758
+ const literalMessage = publicProviderErrorMessage(error);
1492
1759
  return {
1493
1760
  error: {
1494
1761
  code: providerErrorCode(error) ?? "provider_error",
1495
- message: publicProviderErrorMessage(error),
1762
+ message:
1763
+ localizedErrorField(error, "message", literalMessage, localization) ?? literalMessage,
1496
1764
  ...(requestId ? { requestId } : {}),
1497
1765
  retryable: observability.retryable,
1498
1766
  source,
1499
- ...(providerErrorOption(error, "fix") ? { fix: providerErrorOption(error, "fix") } : {}),
1767
+ ...(fix ? { fix } : {}),
1500
1768
  ...(details !== undefined ? { details } : {}),
1501
1769
  },
1502
1770
  };
@@ -1763,6 +2031,11 @@ function publicProviderErrorMessage(error: ProviderError): string {
1763
2031
  }
1764
2032
  if (providerErrorCode(error) === "transport_timeout") return "Request timed out";
1765
2033
  if (providerErrorCode(error) === "transport_network_error") return "Network error";
2034
+ // Provider-local fault: no request was issued, so an upstream-flavored
2035
+ // message would misattribute it (category is provider_error).
2036
+ if (providerErrorCode(error) === "http_header_factory_failed") {
2037
+ return "Request header preparation failed";
2038
+ }
1766
2039
  if (providerErrorCode(error) === "upstream_http_error" && error.status) {
1767
2040
  return `Upstream request failed with status ${error.status}`;
1768
2041
  }
@@ -1933,6 +2206,11 @@ function logProviderError(
1933
2206
  const errorClass = error instanceof Error ? error.name : typeof error;
1934
2207
  const message = error instanceof Error ? error.message : String(error);
1935
2208
  const causeChain = providerErrorCauseChain(error, redact);
2209
+ // Masked 500 bodies carry only errorClass, so the log is the sole channel
2210
+ // that can locate the throw. Declared 5xx (502/504) are included too: their
2211
+ // body carries code+message, but frame[0] is still the throw site inside
2212
+ // provider code. 4xx failures keep the lean record.
2213
+ const stack = status >= 500 ? providerErrorStackFrames(error, redact) : undefined;
1936
2214
  const details = observabilityDetails;
1937
2215
  const isUnregisteredProviderErrorCode =
1938
2216
  status === 500 &&
@@ -1975,6 +2253,7 @@ function logProviderError(
1975
2253
  errorClass: redactDiagnosticText(errorClass, redact),
1976
2254
  message: sanitizeDiagnosticText(redactDiagnosticText(message, redact)),
1977
2255
  ...(causeChain ? { causeChain } : {}),
2256
+ ...(stack ? { stack } : {}),
1978
2257
  ...(providerObservability ? { providerObservability } : {}),
1979
2258
  ...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
1980
2259
  errorCategory: details.category,
@@ -2247,6 +2526,7 @@ function createRequestScope(input: {
2247
2526
  ),
2248
2527
  finished: false,
2249
2528
  staticValues: input.staticSensitiveValues,
2529
+ register: sensitiveRegistry.add,
2250
2530
  };
2251
2531
  const traceConfig = withDiagnosticEnv(observeEnv, () => resolveTraceConfigFromEnv());
2252
2532
  const details = {
@@ -2279,11 +2559,23 @@ function createRequestScope(input: {
2279
2559
  const resolverCollector = new ResolverTelemetryCollector({ redact });
2280
2560
  const nativeCollector = new NativeTelemetryCollector({ redact });
2281
2561
  const httpCollector = new HttpTelemetryCollector({ redact });
2562
+ const stealthCollector = new StealthTelemetryCollector({ redact });
2563
+ const ocrCollector = new OcrTelemetryCollector({ redact });
2564
+ const sttCollector = new SttTelemetryCollector({ redact });
2565
+ const browserCollector = new BrowserTelemetryCollector({ redact });
2566
+ const cacheTelemetry = new CacheTelemetryCollector({ redact });
2567
+ const stateTelemetry = new StateTelemetryCollector({ redact });
2282
2568
  const telemetry = new RequestTelemetry(trace);
2283
2569
  telemetry.register(proxyCollector);
2284
2570
  telemetry.register(resolverCollector);
2285
2571
  telemetry.register(nativeCollector);
2286
2572
  telemetry.register(httpCollector);
2573
+ telemetry.register(stealthCollector);
2574
+ telemetry.register(browserCollector);
2575
+ telemetry.register(ocrCollector);
2576
+ telemetry.register(sttCollector);
2577
+ telemetry.register(cacheTelemetry);
2578
+ telemetry.register(stateTelemetry);
2287
2579
  let rootRunner: <T>(fn: () => Promise<T>) => Promise<T> = (fn) => fn();
2288
2580
  let resolveRoot!: (outcome: RequestTerminalOutcome) => void;
2289
2581
  const rootTerminal = new Promise<RequestTerminalOutcome>((resolve) => {
@@ -2367,6 +2659,12 @@ function createRequestScope(input: {
2367
2659
  resolverTelemetry: resolverCollector,
2368
2660
  nativeTelemetry: nativeCollector,
2369
2661
  httpTelemetry: httpCollector,
2662
+ stealthTelemetry: stealthCollector,
2663
+ ocrTelemetry: ocrCollector,
2664
+ sttTelemetry: sttCollector,
2665
+ browserTelemetry: browserCollector,
2666
+ cacheTelemetry,
2667
+ stateTelemetry,
2370
2668
  seedCredentials(rawBody, kind): void {
2371
2669
  const harvested = rawRequestCredentials(rawBody, kind);
2372
2670
  sensitiveRegistry.add(harvested.values);
@@ -2925,42 +3223,44 @@ function toAuthFlowResponse(
2925
3223
  };
2926
3224
  }
2927
3225
 
2928
- function authFlowLocaleFromHeaders(headers?: Record<string, string>): ProviderLocale {
2929
- const header = Object.entries(headers ?? {}).find(
2930
- ([key]) => key.toLowerCase() === "accept-language",
2931
- )?.[1];
2932
- for (const token of (header ?? "").split(",")) {
2933
- const language = token.trim().split(";")[0]?.split("-")[0]?.toLowerCase();
2934
- if (isAuthFlowLocale(language)) {
2935
- return language;
2936
- }
2937
- }
2938
- return "en";
2939
- }
2940
-
2941
- function isAuthFlowLocale(value: string | undefined): value is ProviderLocale {
2942
- return value === "en" || value === "ko" || value === "ja";
2943
- }
2944
-
2945
3226
  function isAuthTurn(value: unknown): value is AuthTurn {
2946
3227
  return !!value && typeof value === "object" && "kind" in value && "turnId" in value;
2947
3228
  }
2948
3229
 
2949
- function loadAuthFlowLocaleCatalogs(
3230
+ /**
3231
+ * Loads the provider's locale catalogs once, at `createServerApp` time.
3232
+ *
3233
+ * Only the locales that actually exist on disk are read: a provider with
3234
+ * `en`+`ko` but no `ja.json` used to make the whole load throw and silently
3235
+ * disable localization for every turn. Absence of catalogs is not an error —
3236
+ * every localized field falls back to its English literal — but a catalog that
3237
+ * exists and cannot be read is logged once so a packaging mistake is visible at
3238
+ * boot instead of per request.
3239
+ */
3240
+ function resolveProviderLocaleCatalogs(
2950
3241
  provider: ProviderDefinition,
3242
+ options: ProviderServerOptions,
3243
+ logger: ProviderServerLogger,
2951
3244
  ): ProviderLocaleCatalogMap | undefined {
3245
+ if (options.localeCatalogs) return options.localeCatalogs;
2952
3246
  for (const providerDir of [
2953
3247
  process.cwd(),
2954
3248
  join(process.cwd(), "providers", provider.id),
2955
3249
  join(process.cwd(), "providers-staging", provider.id),
2956
3250
  ]) {
2957
- if (!existsSync(join(providerDir, "locales", "en.json"))) continue;
3251
+ const locales = PROVIDER_ERROR_LOCALES.filter((locale) =>
3252
+ existsSync(join(providerDir, "locales", `${locale}.json`)),
3253
+ );
3254
+ if (!locales.includes(DEFAULT_PROVIDER_ERROR_LOCALE)) continue;
2958
3255
  try {
2959
- return loadProviderLocaleCatalogs({
2960
- providerDir,
2961
- locales: AUTH_FLOW_LOCALES,
3256
+ return loadProviderLocaleCatalogs({ providerDir, locales });
3257
+ } catch (error) {
3258
+ logger({
3259
+ level: "warn",
3260
+ event: "provider_locale_catalogs_unavailable",
3261
+ providerId: provider.id,
3262
+ reason: sanitizeDiagnosticText(error instanceof Error ? error.message : String(error)),
2962
3263
  });
2963
- } catch {
2964
3264
  return undefined;
2965
3265
  }
2966
3266
  }
@@ -2968,16 +3268,12 @@ function loadAuthFlowLocaleCatalogs(
2968
3268
  }
2969
3269
 
2970
3270
  function materializeAuthFlowTurn(
2971
- provider: ProviderDefinition,
2972
- request: AuthFlowRequest,
2973
3271
  turn: AuthTurn,
3272
+ catalogs: ProviderLocaleCatalogMap | undefined,
3273
+ locale: ProviderLocale,
2974
3274
  ): AuthTurn {
2975
- const catalogs = loadAuthFlowLocaleCatalogs(provider);
2976
3275
  if (!catalogs) return turn;
2977
- return localizeAuthTurn(turn, {
2978
- catalogs,
2979
- locale: authFlowLocaleFromHeaders(request.headers),
2980
- });
3276
+ return localizeAuthTurn(turn, { catalogs, locale });
2981
3277
  }
2982
3278
 
2983
3279
  function withAuthRequestHeaders(request: AuthFlowRequest, headers: Headers): AuthFlowRequest {
@@ -3088,6 +3384,14 @@ async function handleAuthFlow(
3088
3384
  options: ProviderServerRuntimeOptions,
3089
3385
  state: ProviderRuntimeState,
3090
3386
  scope: RequestScopeContext,
3387
+ /**
3388
+ * Negotiated once per request by the route so a successful turn and an error
3389
+ * envelope from the same request can never disagree. `withAuthRequestHeaders`
3390
+ * lets the HTTP header shadow the envelope's `Accept-Language`, but the
3391
+ * gateway stamps the flow's start locale into the envelope precisely so
3392
+ * continue/poll turns stay in one language, so the envelope wins here.
3393
+ */
3394
+ locale: ProviderLocale,
3091
3395
  signal?: AbortSignal,
3092
3396
  ): Promise<Response | AuthFlowResponse> {
3093
3397
  const flow = provider.auth?.flow;
@@ -3144,7 +3448,7 @@ async function handleAuthFlow(
3144
3448
  !(result instanceof Response) &&
3145
3449
  !(result instanceof ReadableStream) &&
3146
3450
  isAuthTurn(result)
3147
- ? materializeAuthFlowTurn(provider, request, result)
3451
+ ? materializeAuthFlowTurn(result, options.localeCatalogs, locale)
3148
3452
  : result;
3149
3453
  return toAuthFlowResponse(materializedResult, getPatch(), getEngineState());
3150
3454
  } catch (error) {
@@ -3290,7 +3594,7 @@ function parseStatefulForwardingEnvelope(
3290
3594
  rawBody: unknown,
3291
3595
  redact: DiagnosticRedactor,
3292
3596
  ): ProviderServerStatefulForwardEnvelope {
3293
- const parsed = ProviderServerStatefulForwardEnvelopeSchema.safeParse(rawBody);
3597
+ const parsed = ReceivedStatefulForwardEnvelopeSchema.safeParse(rawBody);
3294
3598
  if (parsed.success) return parsed.data;
3295
3599
  throw new ProviderError("Stateful forwarding envelope is invalid.", {
3296
3600
  code: "STATEFUL_FORWARDING_ENVELOPE_INVALID",
@@ -3340,16 +3644,78 @@ export function createServerApp<TContext extends Partial<ProviderContext> = Prov
3340
3644
  );
3341
3645
  }
3342
3646
 
3647
+ /**
3648
+ * Resolve the engine once at boot, before any route exists. An explicit engine
3649
+ * object is used as given; otherwise the resolved mode selects the engine.
3650
+ *
3651
+ * Deliberately never throws for deployment-authored input: a mode this release
3652
+ * cannot serve yields an engine that fails every request and a `/readyz` 503,
3653
+ * because a boot crash would trade a structured signal for CrashLoopBackOff
3654
+ * across the fleet (same trade-off as `provider_secrets_missing` above).
3655
+ */
3656
+ function resolveServerEngine(serverOptions: ProviderServerOptions): {
3657
+ engine: ProviderEngine;
3658
+ resolution: ProviderEngineModeResolution;
3659
+ } {
3660
+ const resolution = resolveProviderEngineMode({
3661
+ option: serverOptions.engineMode,
3662
+ engine: serverOptions.engine,
3663
+ });
3664
+ if (serverOptions.engine !== undefined) return { engine: serverOptions.engine, resolution };
3665
+ return { engine: createEngineForMode(resolution.mode), resolution };
3666
+ }
3667
+
3668
+ /**
3669
+ * `engine_env_ignored` is expected for the whole migration window: the manifest
3670
+ * generator projects the remote engine env fleet-wide before any mode flips, so
3671
+ * escalating on it would turn every healthy boot into a warn line. The
3672
+ * disagreement warnings still escalate.
3673
+ */
3674
+ const ENGINE_MODE_INFO_WARNINGS: ReadonlySet<string> = new Set(["engine_env_ignored"]);
3675
+
3676
+ function engineModeLogEvent(
3677
+ provider: ProviderDefinition,
3678
+ resolution: ProviderEngineModeResolution,
3679
+ attached: boolean,
3680
+ ): ProviderServerLogEvent {
3681
+ const escalating = resolution.warnings.filter(
3682
+ (warning) => !ENGINE_MODE_INFO_WARNINGS.has(warning),
3683
+ );
3684
+ return {
3685
+ level: attached && escalating.length === 0 ? "info" : "warn",
3686
+ event: "provider_engine_mode",
3687
+ providerId: provider.id,
3688
+ mode: resolution.mode,
3689
+ source: resolution.source,
3690
+ deprecated: resolution.deprecated,
3691
+ attached,
3692
+ sdkVersion: SDK_VERSION,
3693
+ ...(provider.runtimeTarget === undefined ? {} : { runtimeTarget: provider.runtimeTarget }),
3694
+ ...(resolution.warnings.length === 0 ? {} : { warnings: resolution.warnings }),
3695
+ };
3696
+ }
3697
+
3343
3698
  function createServerAppWithCapabilityModules(
3344
3699
  provider: ProviderDefinition,
3345
3700
  serverOptions: ProviderServerOptions,
3346
3701
  capabilityModules: ProviderCapabilityModules,
3347
3702
  ): Hono {
3703
+ const engineSelection = resolveServerEngine(serverOptions);
3704
+ // Read the provider's locale catalogs once here rather than per turn: the
3705
+ // previous auth-flow loader re-read all three JSON files on every turn and
3706
+ // threw the whole map away when one locale file was absent.
3707
+ const localeCatalogs = resolveProviderLocaleCatalogs(
3708
+ provider,
3709
+ serverOptions,
3710
+ serverOptions.logger ?? defaultProviderServerLogger,
3711
+ );
3348
3712
  const options: ProviderServerRuntimeOptions = {
3349
3713
  ...serverOptions,
3714
+ ...(localeCatalogs ? { localeCatalogs } : {}),
3350
3715
  capabilityModules,
3351
- engine: serverOptions.engine ?? createInProcessProviderEngine(),
3716
+ engine: engineSelection.engine,
3352
3717
  };
3718
+ const engineAttached = !isUnavailableProviderEngine(options.engine);
3353
3719
  const app = new Hono();
3354
3720
  // Compile the startup inventory once, composed with the shared outside-context fallback.
3355
3721
  const staticSensitiveValues = compileProcessDiagnosticSensitiveValues(
@@ -3392,11 +3758,27 @@ function createServerAppWithCapabilityModules(
3392
3758
  const error = new ProviderError("Not found", { code: "not_found", retryable: false });
3393
3759
  const observabilityDetails = errorObservabilityDetails(error);
3394
3760
  return responseWithErrorObservability(
3395
- c.json(toErrorResponse(error, undefined, observabilityDetails), 404),
3761
+ c.json(
3762
+ toErrorResponse(
3763
+ error,
3764
+ undefined,
3765
+ observabilityDetails,
3766
+ undefined,
3767
+ errorEnvelopeLocalization({
3768
+ catalogs: localeCatalogs,
3769
+ requestHeaders: c.req.raw.headers,
3770
+ }),
3771
+ ),
3772
+ 404,
3773
+ ),
3396
3774
  observabilityDetails,
3397
3775
  );
3398
3776
  });
3399
3777
 
3778
+ // Liveness. Deliberately static and engine-blind: generated manifests point
3779
+ // liveness at this route, so making it depend on engine reachability would
3780
+ // restart every provider pod on one engine incident. Engine state belongs to
3781
+ // /readyz, which manifests wire to readiness and startup only.
3400
3782
  app.get("/health", (c) =>
3401
3783
  c.json({
3402
3784
  status: "ok",
@@ -3405,6 +3787,25 @@ function createServerAppWithCapabilityModules(
3405
3787
  }),
3406
3788
  );
3407
3789
 
3790
+ // Readiness. Reports the boot-resolved attachment; does not probe the engine
3791
+ // per call, so it cannot amplify an engine incident into request load.
3792
+ app.get("/readyz", (c) =>
3793
+ c.json(
3794
+ {
3795
+ status: engineAttached ? "ok" : "unavailable",
3796
+ provider: provider.id,
3797
+ version: provider.version,
3798
+ engine: {
3799
+ // The resolution, not the raw engine `kind`: normalized, so this
3800
+ // body and the `provider_engine_mode` boot event never disagree.
3801
+ mode: engineSelection.resolution.mode,
3802
+ attached: engineAttached,
3803
+ },
3804
+ },
3805
+ engineAttached ? 200 : 503,
3806
+ ),
3807
+ );
3808
+
3408
3809
  app.post(STATEFUL_INTERNAL_OPERATIONS_ROUTE, async (c) => {
3409
3810
  let rawBodyText = "";
3410
3811
  let rawBody: unknown;
@@ -3535,7 +3936,10 @@ function createServerAppWithCapabilityModules(
3535
3936
  requestId: request.requestId,
3536
3937
  operationId,
3537
3938
  headers: request.headers ?? {},
3538
- correlation: { connectionId: envelope.connectionId },
3939
+ correlation: {
3940
+ connectionId: envelope.connectionId,
3941
+ tenantId: resolveOperationTenantId(request),
3942
+ },
3539
3943
  });
3540
3944
  const response = await requestScope.run(async () => {
3541
3945
  const ctx = createProviderContext(
@@ -3580,7 +3984,18 @@ function createServerAppWithCapabilityModules(
3580
3984
  });
3581
3985
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
3582
3986
  const response = c.json(
3583
- toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
3987
+ toErrorResponse(
3988
+ error,
3989
+ requestId,
3990
+ observabilityDetails,
3991
+ requestScope.redact,
3992
+ errorEnvelopeLocalization({
3993
+ catalogs: localeCatalogs,
3994
+ requestHeaders: c.req.raw.headers,
3995
+ rawBody,
3996
+ ...(declaredErrorCode ? { declaredErrorCode } : {}),
3997
+ }),
3998
+ ),
3584
3999
  status,
3585
4000
  );
3586
4001
  return finalizeRequestResponse(requestScope, response, {
@@ -3617,7 +4032,10 @@ function createServerAppWithCapabilityModules(
3617
4032
  requestScope.enrich({
3618
4033
  requestId: body.requestId,
3619
4034
  headers: body.headers,
3620
- correlation: { connectionId: resolveOperationConnectionId(body) },
4035
+ correlation: {
4036
+ connectionId: resolveOperationConnectionId(body),
4037
+ tenantId: resolveOperationTenantId(body),
4038
+ },
3621
4039
  });
3622
4040
  const streaming = provider.operations[operation]?.transport?.kind
3623
4041
  ? provider.operations[operation]?.transport?.kind !== "json"
@@ -3658,7 +4076,18 @@ function createServerAppWithCapabilityModules(
3658
4076
  requestScope.enrich({ ...(requestId ? { requestId } : {}) });
3659
4077
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
3660
4078
  const response = c.json(
3661
- toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
4079
+ toErrorResponse(
4080
+ error,
4081
+ requestId,
4082
+ observabilityDetails,
4083
+ requestScope.redact,
4084
+ errorEnvelopeLocalization({
4085
+ catalogs: localeCatalogs,
4086
+ requestHeaders: c.req.raw.headers,
4087
+ rawBody,
4088
+ ...(declaredErrorCode ? { declaredErrorCode } : {}),
4089
+ }),
4090
+ ),
3662
4091
  status,
3663
4092
  );
3664
4093
  return finalizeRequestResponse(requestScope, response, {
@@ -3684,6 +4113,12 @@ function createServerAppWithCapabilityModules(
3684
4113
  for (const { path, flowRoute, logRoute } of authRoutes) {
3685
4114
  app.post(path, async (c) => {
3686
4115
  let rawBody: unknown;
4116
+ // Negotiated once and shared by the success and failure paths so an
4117
+ // auth turn and an error envelope from the same request agree.
4118
+ let localization = errorEnvelopeLocalization({
4119
+ catalogs: localeCatalogs,
4120
+ requestHeaders: c.req.raw.headers,
4121
+ });
3687
4122
  const requestScope = createRequestScope({
3688
4123
  provider,
3689
4124
  staticSensitiveValues,
@@ -3698,6 +4133,11 @@ function createServerAppWithCapabilityModules(
3698
4133
  .clone()
3699
4134
  .json()
3700
4135
  .catch(() => undefined);
4136
+ localization = errorEnvelopeLocalization({
4137
+ catalogs: localeCatalogs,
4138
+ requestHeaders: c.req.raw.headers,
4139
+ rawBody,
4140
+ });
3701
4141
  requestScope.seedCredentials(rawBody, "auth");
3702
4142
  const body = withAuthRequestHeaders(
3703
4143
  AuthFlowRequestSchema.parse(rawBody),
@@ -3710,7 +4150,7 @@ function createServerAppWithCapabilityModules(
3710
4150
  correlation: {
3711
4151
  connectionId: resolveOperationConnectionId(body),
3712
4152
  flowId: body.flowId,
3713
- tenantId: body.tenantId,
4153
+ tenantId: normalizeIdentifier(body.tenantId),
3714
4154
  requestedProviderId:
3715
4155
  body.providerId !== undefined && body.providerId !== provider.id
3716
4156
  ? body.providerId
@@ -3725,6 +4165,7 @@ function createServerAppWithCapabilityModules(
3725
4165
  options,
3726
4166
  state,
3727
4167
  requestScope as RequestScope,
4168
+ localization.locale,
3728
4169
  c.req.raw.signal,
3729
4170
  );
3730
4171
  return handled instanceof Response ? handled : c.json(handled);
@@ -3739,7 +4180,13 @@ function createServerAppWithCapabilityModules(
3739
4180
  requestScope.enrich({ ...(requestId ? { requestId } : {}) });
3740
4181
  const observabilityDetails = errorObservabilityDetails(error);
3741
4182
  const response = c.json(
3742
- toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
4183
+ toErrorResponse(
4184
+ error,
4185
+ requestId,
4186
+ observabilityDetails,
4187
+ requestScope.redact,
4188
+ localization,
4189
+ ),
3743
4190
  status,
3744
4191
  );
3745
4192
  return finalizeRequestResponse(requestScope, response, {
@@ -3876,13 +4323,18 @@ export async function serve<TContext extends Partial<ProviderContext> = Provider
3876
4323
  );
3877
4324
  const configuredSignals = resolveShutdownSignals(options.shutdown?.signals ?? true);
3878
4325
  const selfTestSecrets = resolveSelfTestMasterSecrets();
4326
+ // Resolve the engine here so the boot event reports the truthful source
4327
+ // (option/env/default) and the app is handed the engine already selected —
4328
+ // one resolution per process, not one per app construction.
4329
+ const engineSelection = resolveServerEngine(options as unknown as ProviderServerOptions);
3879
4330
  const serverAppOptions: ProviderServerOptions<TContext> = {
3880
4331
  logger: options.logger,
3881
- engine: options.engine,
4332
+ engine: engineSelection.engine,
3882
4333
  files: options.files,
3883
4334
  ocr: options.ocr,
3884
4335
  stt: options.stt,
3885
4336
  resolver: options.resolver,
4337
+ localeCatalogs: options.localeCatalogs,
3886
4338
  state: options.state,
3887
4339
  allowMemoryStateFallback: options.allowMemoryStateFallback,
3888
4340
  operationExecutor: options.operationExecutor,
@@ -3893,6 +4345,15 @@ export async function serve<TContext extends Partial<ProviderContext> = Provider
3893
4345
  createServerAppAsync(provider, serverAppOptions),
3894
4346
  selfTestSecrets ? import("./self-test.js") : undefined,
3895
4347
  ]);
4348
+ // One structured line per process boot, before the listener binds: the fleet
4349
+ // audit (ADR-0011 migration) counts these.
4350
+ logger(
4351
+ engineModeLogEvent(
4352
+ provider as unknown as ProviderDefinition,
4353
+ engineSelection.resolution,
4354
+ !isUnavailableProviderEngine(engineSelection.engine),
4355
+ ),
4356
+ );
3896
4357
 
3897
4358
  const servers: BunServerHandle[] = [];
3898
4359
  try {