@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";
@@ -12,14 +12,22 @@ import { z } from "zod";
12
12
  import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
13
13
  import { validateFailClosedDeclaration } from "../declaration-validation.js";
14
14
  import {
15
- createInProcessProviderEngine,
16
15
  ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES,
17
16
  isEngineOwnedEnvName,
18
17
  type ProviderEngine,
19
18
  type ProviderEngineBindingCandidates,
19
+ type ProviderEngineMode,
20
20
  readEngineProxyCredentials,
21
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";
22
29
  import { safeProviderErrorObservability } from "../error-observability.js";
30
+ import { providerErrorStackFrames } from "./error-stack-frames.js";
23
31
  import {
24
32
  SDK_OWNED_PROVIDER_ERROR_CODES,
25
33
  SDK_RUNTIME_OWNED_ERROR_CODES,
@@ -42,6 +50,15 @@ import {
42
50
  localizeAuthTurn,
43
51
  type ProviderLocaleCatalogMap,
44
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";
45
62
  import type { ProviderLocale } from "../i18n/keys.js";
46
63
  import {
47
64
  categoryForStatus,
@@ -54,6 +71,7 @@ import {
54
71
  import { createScratchpad } from "../runtime/auth-flow.js";
55
72
  import type * as BrowserRuntimeModule from "../runtime/browser.js";
56
73
  import { createProviderCache } from "../runtime/cache.js";
74
+ import { bindCacheTelemetry, CacheTelemetryCollector } from "../runtime/cache-telemetry.js";
57
75
  import type { HandleTelemetryEvent } from "../handle.js";
58
76
  import { createHandleContext } from "../runtime/handle.js";
59
77
  import { createCredentialContext } from "../runtime/credential.js";
@@ -77,9 +95,17 @@ import {
77
95
  import { createEnvContext } from "../runtime/env.js";
78
96
  import { executeOperation } from "../runtime/executor.js";
79
97
  import { createHttpClient } from "../runtime/http.js";
98
+ import { NativeTelemetryCollector } from "../runtime/native-telemetry.js";
99
+ import {
100
+ bindHttpTelemetry,
101
+ isSupportedHttpBinding,
102
+ warnUnsupportedHttpBinding,
103
+ } from "../runtime/http-telemetry-binding.js";
104
+ import { HttpTelemetryCollector, type HttpTelemetryLogPayload } from "../runtime/http-telemetry.js";
80
105
  import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
81
106
  import type * as NativeNetworkRuntimeModule from "../runtime/native-network.js";
82
107
  import { createOcrClientFromEnv } from "../runtime/ocr.js";
108
+ import { bindOcrTelemetry, OcrTelemetryCollector } from "../runtime/ocr-telemetry.js";
83
109
  import { getProviderBaseUrl } from "../runtime/provider.js";
84
110
  import {
85
111
  PROXY_AUTH_IP_DENIED_CODE,
@@ -110,6 +136,10 @@ import {
110
136
  ResolverTelemetryCollector,
111
137
  type ResolverTelemetryLogPayload,
112
138
  } from "../runtime/resolver-telemetry.js";
139
+ import {
140
+ createResolverRuntimeOptions,
141
+ resolveNativeProxyPolicy,
142
+ } from "../runtime/resolver-runtime-options.js";
113
143
  import {
114
144
  assertRequiredSecretsPresent,
115
145
  listMissingRequiredSecrets,
@@ -119,10 +149,22 @@ import {
119
149
  createProviderRuntimeStateFromEnv,
120
150
  createUnsupportedProviderRuntimeState,
121
151
  } from "../runtime/state.js";
152
+ import {
153
+ StateTelemetryCollector,
154
+ instrumentProviderRuntimeState,
155
+ } from "../runtime/state-telemetry.js";
122
156
  import type * as StealthRuntimeModule from "../runtime/stealth.js";
123
157
  import type { StealthChallengeRuntime } from "../runtime/stealth-akamai-sbsd.js";
124
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";
125
166
  import { createSttClientFromEnv } from "../runtime/stt.js";
167
+ import { bindSttTelemetry, SttTelemetryCollector } from "../runtime/stt-telemetry.js";
126
168
  import {
127
169
  createTraceContext,
128
170
  getTraceRecorder,
@@ -153,7 +195,6 @@ import type {
153
195
  BrowserClient,
154
196
  FlowContext,
155
197
  FlowContextStore,
156
- HttpRetrySummary,
157
198
  OcrContext,
158
199
  OperationDefinition,
159
200
  OperationErrorCode,
@@ -164,8 +205,8 @@ import type {
164
205
  ProviderDefinition,
165
206
  ProviderErrorStatus,
166
207
  ProviderFilesContext,
167
- ProviderProxyPolicy,
168
208
  ProviderRuntimeState,
209
+ ProviderRuntimeTarget,
169
210
  ProviderStreamEvent,
170
211
  ResolverContext,
171
212
  StealthClient,
@@ -233,30 +274,7 @@ function providerErrorCode(error: unknown): string | undefined {
233
274
  }
234
275
  }
235
276
 
236
- const AUTH_FLOW_LOCALES = ["en", "ko", "ja"] as const;
237
- const retryResponseMeta = new WeakMap<ProviderContext, HttpRetrySummary>();
238
- type RetryLastErrorCode =
239
- // runtime/http.ts:178-179 preserves ambient cancellation.
240
- | "transport_cancelled"
241
- // runtime/http.ts:180-196 normalizes timeout aborts and timeout-shaped errors.
242
- | "transport_timeout"
243
- // runtime/http.ts:198-225 normalizes other native fetch failures.
244
- | "transport_network_error"
245
- // runtime/http.ts:119-126,763-771,829-837 creates upstream status errors.
246
- | "upstream_http_error"
247
- // runtime/proxy-retry-policy.ts:64-84 forwards other coded TransportErrors; the builder closes them.
248
- | "other";
249
- const RETRY_LAST_ERROR_CODES = [
250
- "transport_cancelled",
251
- "transport_timeout",
252
- "transport_network_error",
253
- "upstream_http_error",
254
- "other",
255
- ] as const satisfies readonly RetryLastErrorCode[];
256
-
257
- function tenantRetryLastErrorCode(value: string): RetryLastErrorCode {
258
- return RETRY_LAST_ERROR_CODES.find((candidate) => candidate === value) ?? "other";
259
- }
277
+ const retryResponseMeta = new WeakMap<ProviderContext, HttpTelemetryCollector>();
260
278
  const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
261
279
  const STATEFUL_FORWARDING_SOURCE_POD_HEADER = "x-apifuse-stateful-source-pod";
262
280
  const DEFAULT_STATEFUL_FORWARDING_MAX_SKEW_MS = 5 * 60_000;
@@ -289,6 +307,22 @@ export const ProviderServerStatefulForwardEnvelopeSchema = z
289
307
  })
290
308
  .strict();
291
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
+
292
326
  export type ProviderServerStatefulForwardEnvelope = Readonly<
293
327
  z.infer<typeof ProviderServerStatefulForwardEnvelopeSchema>
294
328
  >;
@@ -616,36 +650,6 @@ function getProviderStealthProfile(provider: ProviderDefinition) {
616
650
 
617
651
  type ResolverRuntimeOptions = ResolverRuntimeModule.ResolverRuntimeOptions;
618
652
 
619
- /** One option set for ctx.resolver and the automatic SBSD solve, so both share identity scope, cache, proxy intent, and telemetry. */
620
- function createResolverRuntimeOptions(
621
- provider: ProviderDefinition,
622
- cache: ReturnType<typeof createProviderCache>,
623
- identityScope: string,
624
- proxyPolicy: ProviderProxyPolicy | undefined,
625
- proxyClientOptions: Omit<
626
- NonNullable<ResolverRuntimeOptions["proxyIntent"]>,
627
- "mode" | "userAgent"
628
- >,
629
- stealthProfile: { readonly userAgent: string } | undefined,
630
- telemetry: ResolverTelemetryCollector,
631
- ): ResolverRuntimeOptions {
632
- return {
633
- allowedHosts: provider.allowedHosts,
634
- cache,
635
- telemetry,
636
- identityScope,
637
- ...(proxyPolicy
638
- ? {
639
- proxyIntent: {
640
- mode: proxyPolicy.mode,
641
- ...proxyClientOptions,
642
- ...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
643
- },
644
- }
645
- : {}),
646
- };
647
- }
648
-
649
653
  /** Shared predicate for the SBSD challenge runtime and the ceremony egress lease. */
650
654
  function declaresAkamaiSbsdResolver(provider: ProviderDefinition): boolean {
651
655
  return provider.resolver?.kinds.some((kind) => kind === "akamai_sbsd") === true;
@@ -677,12 +681,17 @@ function createStealthChallengeDetection(
677
681
  async solve(challenge, transport, solveSignal) {
678
682
  const resolver = resolverRuntime.bindResolverSignal(
679
683
  (resolverOverride
680
- ? bindResolverTelemetry(resolverOverride, resolverOptions.telemetry)
684
+ ? bindResolverTelemetry(
685
+ resolverOverride,
686
+ resolverOptions.telemetry,
687
+ resolverOptions.browserTelemetry,
688
+ )
681
689
  : undefined) ??
682
690
  resolverRuntime.createResolverClientFromEnv(provider.resolver, undefined, {
683
691
  allowedHosts: resolverOptions.allowedHosts,
684
692
  cache: resolverOptions.cache,
685
693
  telemetry: resolverOptions.telemetry,
694
+ browserTelemetry: resolverOptions.browserTelemetry,
686
695
  identityScope: resolverOptions.identityScope,
687
696
  // No proxyIntent: the transport is already bound to the initiating
688
697
  // request's lease, and a second identity resolution would allocate
@@ -817,20 +826,18 @@ function resolveOperationConnectionId(
817
826
  // absent so it can never override a valid id or key a real scope. Requests
818
827
  // without any usable id fall back to the documented missing-connection
819
828
  // sentinel scope instead of scoping context/affinity/state under "".
820
- return (
821
- normalizeConnectionId(request.connection?.id) ?? normalizeConnectionId(request.connectionId)
822
- );
829
+ return normalizeIdentifier(request.connection?.id) ?? normalizeIdentifier(request.connectionId);
823
830
  }
824
831
 
825
- function normalizeConnectionId(id: string | undefined): string | undefined {
826
- 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);
827
837
  }
828
838
 
829
- function resolveNativeProxyPolicy(provider: ProviderDefinition): ProviderProxyPolicy | undefined {
830
- if (typeof provider.proxy === "object") return provider.proxy;
831
- if (provider.proxy === true) return { mode: "optional" };
832
- if (provider.proxy === false) return { mode: "disabled" };
833
- return undefined;
839
+ function normalizeIdentifier(id: string | undefined): string | undefined {
840
+ return id === "" ? undefined : id;
834
841
  }
835
842
 
836
843
  function providerSecretNames(provider: ProviderDefinition): string[] {
@@ -840,11 +847,19 @@ function providerSecretNames(provider: ProviderDefinition): string[] {
840
847
  }
841
848
 
842
849
  type RequestScopeContext = {
850
+ run<T>(fn: () => Promise<T>): Promise<T>;
843
851
  redact: DiagnosticRedactor;
844
852
  trace: RuntimeTraceContext;
845
853
  telemetry: RequestTelemetry;
846
854
  resolverTelemetry: ResolverTelemetryCollector;
847
855
  nativeTelemetry: NativeTelemetryCollector;
856
+ httpTelemetry: HttpTelemetryCollector;
857
+ stealthTelemetry: StealthTelemetryCollector;
858
+ ocrTelemetry: OcrTelemetryCollector;
859
+ sttTelemetry: SttTelemetryCollector;
860
+ browserTelemetry: BrowserTelemetryCollector;
861
+ cacheTelemetry: CacheTelemetryCollector;
862
+ stateTelemetry: StateTelemetryCollector;
848
863
  };
849
864
 
850
865
  function createProviderContext(
@@ -872,8 +887,9 @@ function createProviderContext(
872
887
  proxyClientOptions.affinityKey,
873
888
  request.requestId,
874
889
  );
875
- const cache = createProviderCache({ providerId: provider.id });
876
- let wrappedContext: ProviderContext | undefined;
890
+ const cache = createProviderCache(
891
+ bindCacheTelemetry({ providerId: provider.id }, scope.cacheTelemetry),
892
+ );
877
893
  const { capabilityModules } = options;
878
894
  const resolverOptions = createResolverRuntimeOptions(
879
895
  provider,
@@ -883,6 +899,7 @@ function createProviderContext(
883
899
  proxyClientOptions,
884
900
  stealthProfile,
885
901
  scope.resolverTelemetry,
902
+ scope.browserTelemetry,
886
903
  );
887
904
  const challengeRuntime = createStealthChallengeDetection(
888
905
  provider,
@@ -893,6 +910,7 @@ function createProviderContext(
893
910
  signal,
894
911
  );
895
912
  const stealthClientOptions = {
913
+ stealthTelemetry: scope.stealthTelemetry,
896
914
  upstream: proxyClientOptions.upstream,
897
915
  affinityKey: proxyClientOptions.affinityKey,
898
916
  telemetry: scope.telemetry.proxy,
@@ -928,11 +946,19 @@ function createProviderContext(
928
946
  scopes: request.connection?.scopes,
929
947
  values: request.connection?.secrets,
930
948
  });
949
+ const operationTenantId = resolveOperationTenantId(request);
931
950
  const requestContext = {
932
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 } : {}),
933
955
  headers: request.headers ?? {},
934
956
  };
935
- const requestState = state.forConnection(requestContext.connectionId);
957
+ const requestState = instrumentProviderRuntimeState(
958
+ state,
959
+ scope.stateTelemetry,
960
+ scope.redact,
961
+ ).forConnection(requestContext.connectionId);
936
962
  const bindings: ProviderEngineBindingCandidates = {
937
963
  env,
938
964
  credential,
@@ -941,10 +967,7 @@ function createProviderContext(
941
967
  http: createHttpClient(baseUrl, {
942
968
  ...proxyClientOptions,
943
969
  ...(signal ? { signal } : {}),
944
- onRetrySummary: (summary) => {
945
- if (summary.attempts <= 1 || !wrappedContext) return;
946
- retryResponseMeta.set(wrappedContext, summary);
947
- },
970
+ httpTelemetry: scope.httpTelemetry,
948
971
  }),
949
972
  cache,
950
973
  state: requestState,
@@ -962,6 +985,7 @@ function createProviderContext(
962
985
  requireCdpPool: isProductionProviderBrowserMode(provider),
963
986
  stealth: true,
964
987
  engine: provider.browser?.engine,
988
+ telemetry: scope.browserTelemetry,
965
989
  })
966
990
  : createBrowserStub(),
967
991
  ...(provider.native
@@ -982,12 +1006,22 @@ function createProviderContext(
982
1006
  : {}),
983
1007
  trace: scope.trace,
984
1008
  auth: createAuthStub(),
985
- ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
986
- 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
+ ),
987
1017
  resolver: capabilityModules.resolver
988
1018
  ? capabilityModules.resolver.bindResolverSignal(
989
1019
  (options.resolver
990
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
1020
+ ? bindResolverTelemetry(
1021
+ options.resolver,
1022
+ resolverOptions.telemetry,
1023
+ resolverOptions.browserTelemetry,
1024
+ )
991
1025
  : undefined) ??
992
1026
  capabilityModules.resolver.createResolverClientFromEnv(
993
1027
  provider.resolver,
@@ -998,7 +1032,11 @@ function createProviderContext(
998
1032
  )
999
1033
  : bindResolverSignalWithoutRuntime(
1000
1034
  (options.resolver
1001
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
1035
+ ? bindResolverTelemetry(
1036
+ options.resolver,
1037
+ resolverOptions.telemetry,
1038
+ resolverOptions.browserTelemetry,
1039
+ )
1002
1040
  : undefined) ??
1003
1041
  createUnsupportedResolverClient("Provider does not declare resolver capability"),
1004
1042
  signal,
@@ -1015,10 +1053,43 @@ function createProviderContext(
1015
1053
  }),
1016
1054
  }),
1017
1055
  };
1018
- const context = wrapWithInstrumentation(
1019
- options.engine.attach({ provider, bindings }) as ProviderContext,
1020
- );
1021
- wrappedContext = context;
1056
+ const candidateHttp = bindings.http;
1057
+ const candidateBrowser = bindings.browser;
1058
+ const attached = options.engine.attach({ provider, bindings }) as ProviderContext;
1059
+ const hostHttp = provider.http ? attached.http : undefined;
1060
+ const supportedHostHttp = provider.http && isSupportedHttpBinding(hostHttp);
1061
+ if (provider.http && !supportedHostHttp) warnUnsupportedHttpBinding();
1062
+ const observedHttp =
1063
+ supportedHostHttp && hostHttp !== candidateHttp
1064
+ ? bindHttpTelemetry(hostHttp, scope.httpTelemetry)
1065
+ : undefined;
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
1076
+ ? new Proxy(attached, {
1077
+ 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);
1082
+ },
1083
+ })
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;
1091
+ const context = wrapWithInstrumentation(contextTarget);
1092
+ retryResponseMeta.set(context, scope.httpTelemetry);
1022
1093
  return context;
1023
1094
  }
1024
1095
 
@@ -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,
@@ -1199,13 +1274,30 @@ function createAuthFlowContext(
1199
1274
  http: createHttpClient(baseUrl, {
1200
1275
  ...proxyClientOptions,
1201
1276
  ...(signal ? { signal } : {}),
1277
+ httpTelemetry: scope.httpTelemetry,
1202
1278
  }),
1203
- state: state.forConnection(resolveOperationConnectionId(request)),
1279
+ state: instrumentProviderRuntimeState(
1280
+ state,
1281
+ scope.stateTelemetry,
1282
+ scope.redact,
1283
+ ).forConnection(resolveOperationConnectionId(request)),
1204
1284
  stealth: stealthBaseUrl
1205
1285
  ? capabilityModules.stealth
1206
1286
  ? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
1207
1287
  : createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
1208
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(),
1209
1301
  ...(provider.native
1210
1302
  ? {
1211
1303
  native: {
@@ -1228,12 +1320,22 @@ function createAuthFlowContext(
1228
1320
  ]),
1229
1321
  credential,
1230
1322
  context: flowContextStore.context,
1231
- ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
1232
- 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
+ ),
1233
1331
  resolver: capabilityModules.resolver
1234
1332
  ? capabilityModules.resolver.bindResolverSignal(
1235
1333
  (options.resolver
1236
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
1334
+ ? bindResolverTelemetry(
1335
+ options.resolver,
1336
+ resolverOptions.telemetry,
1337
+ resolverOptions.browserTelemetry,
1338
+ )
1237
1339
  : undefined) ??
1238
1340
  capabilityModules.resolver.createResolverClientFromEnv(
1239
1341
  provider.resolver,
@@ -1244,7 +1346,11 @@ function createAuthFlowContext(
1244
1346
  )
1245
1347
  : bindResolverSignalWithoutRuntime(
1246
1348
  (options.resolver
1247
- ? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
1349
+ ? bindResolverTelemetry(
1350
+ options.resolver,
1351
+ resolverOptions.telemetry,
1352
+ resolverOptions.browserTelemetry,
1353
+ )
1248
1354
  : undefined) ??
1249
1355
  createUnsupportedResolverClient("Provider does not declare resolver capability"),
1250
1356
  signal,
@@ -1288,6 +1394,7 @@ type ProviderServerLogEventBase = ProviderRequestCost & {
1288
1394
  status: number;
1289
1395
  proxy?: ProxyTelemetryLogPayload;
1290
1396
  resolver?: ResolverTelemetryLogPayload;
1397
+ http?: HttpTelemetryLogPayload;
1291
1398
  };
1292
1399
 
1293
1400
  export type ProviderServerLogEvent =
@@ -1307,6 +1414,7 @@ export type ProviderServerLogEvent =
1307
1414
  retryable?: boolean;
1308
1415
  providerObservability?: ProviderErrorObservability;
1309
1416
  causeChain?: ProviderErrorCauseFrame[];
1417
+ stack?: string[];
1310
1418
  signal?: "unregistered_provider_error_code";
1311
1419
  signalFix?: string;
1312
1420
  issues?: Array<{ path: string; code: string; message: string }>;
@@ -1317,6 +1425,13 @@ export type ProviderServerLogEvent =
1317
1425
  providerId: string;
1318
1426
  missingSecrets: string[];
1319
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
+ }
1320
1435
  | ({
1321
1436
  level: "info";
1322
1437
  event: "provider_handle";
@@ -1340,6 +1455,27 @@ export type ProviderServerLogEvent =
1340
1455
  errorClass: string;
1341
1456
  message: string;
1342
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
+ }
1343
1479
  | SelfTestCancellationLogEvent;
1344
1480
 
1345
1481
  export type ProviderServerLogger = (event: ProviderServerLogEvent) => void;
@@ -1348,6 +1484,17 @@ export type ProviderServerOptions<TContext extends Partial<ProviderContext> = Pr
1348
1484
  logger?: ProviderServerLogger;
1349
1485
  /** Capability attachment boundary. Defaults to the local in-process engine. */
1350
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;
1351
1498
  /** Request-file resolver supplied by the engine host when `files` is declared. */
1352
1499
  files?: ProviderFilesContext;
1353
1500
  /** Optional provider-specific operation executor. Stateful providers use this to preserve provider-local runtime semantics. */
@@ -1371,6 +1518,15 @@ export type ProviderServerOptions<TContext extends Partial<ProviderContext> = Pr
1371
1518
  * resolver is reported as one custom invocation; its internal vendor work is not exposed.
1372
1519
  */
1373
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;
1374
1530
  /** Optional runtime state override for tests or custom hosts. Production resolves Redis from env and fails closed when unavailable. */
1375
1531
  state?: ProviderRuntimeState;
1376
1532
  /** Allow process-local runtime state only for local development and tests. */
@@ -1465,11 +1621,116 @@ function publicErrorSource(error: unknown, category: ProviderErrorCategory): Pro
1465
1621
  return sourceForCategory(category);
1466
1622
  }
1467
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
+
1468
1728
  function toErrorResponse(
1469
1729
  error: unknown,
1470
1730
  requestId: string | undefined,
1471
1731
  observabilityDetails: ErrorObservabilityDetails,
1472
1732
  redact?: DiagnosticRedactor,
1733
+ localization?: ErrorEnvelopeLocalization,
1473
1734
  ): OperationErrorResponse {
1474
1735
  const observability = observabilityDetails;
1475
1736
  const source = publicErrorSource(error, observability.category);
@@ -1487,14 +1748,23 @@ function toErrorResponse(
1487
1748
 
1488
1749
  if (isProviderError(error)) {
1489
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);
1490
1759
  return {
1491
1760
  error: {
1492
1761
  code: providerErrorCode(error) ?? "provider_error",
1493
- message: publicProviderErrorMessage(error),
1762
+ message:
1763
+ localizedErrorField(error, "message", literalMessage, localization) ?? literalMessage,
1494
1764
  ...(requestId ? { requestId } : {}),
1495
1765
  retryable: observability.retryable,
1496
1766
  source,
1497
- ...(providerErrorOption(error, "fix") ? { fix: providerErrorOption(error, "fix") } : {}),
1767
+ ...(fix ? { fix } : {}),
1498
1768
  ...(details !== undefined ? { details } : {}),
1499
1769
  },
1500
1770
  };
@@ -1761,6 +2031,11 @@ function publicProviderErrorMessage(error: ProviderError): string {
1761
2031
  }
1762
2032
  if (providerErrorCode(error) === "transport_timeout") return "Request timed out";
1763
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
+ }
1764
2039
  if (providerErrorCode(error) === "upstream_http_error" && error.status) {
1765
2040
  return `Upstream request failed with status ${error.status}`;
1766
2041
  }
@@ -1931,6 +2206,11 @@ function logProviderError(
1931
2206
  const errorClass = error instanceof Error ? error.name : typeof error;
1932
2207
  const message = error instanceof Error ? error.message : String(error);
1933
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;
1934
2214
  const details = observabilityDetails;
1935
2215
  const isUnregisteredProviderErrorCode =
1936
2216
  status === 500 &&
@@ -1973,6 +2253,7 @@ function logProviderError(
1973
2253
  errorClass: redactDiagnosticText(errorClass, redact),
1974
2254
  message: sanitizeDiagnosticText(redactDiagnosticText(message, redact)),
1975
2255
  ...(causeChain ? { causeChain } : {}),
2256
+ ...(stack ? { stack } : {}),
1976
2257
  ...(providerObservability ? { providerObservability } : {}),
1977
2258
  ...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
1978
2259
  errorCategory: details.category,
@@ -2245,6 +2526,7 @@ function createRequestScope(input: {
2245
2526
  ),
2246
2527
  finished: false,
2247
2528
  staticValues: input.staticSensitiveValues,
2529
+ register: sensitiveRegistry.add,
2248
2530
  };
2249
2531
  const traceConfig = withDiagnosticEnv(observeEnv, () => resolveTraceConfigFromEnv());
2250
2532
  const details = {
@@ -2276,10 +2558,24 @@ function createRequestScope(input: {
2276
2558
  const proxyCollector = new ProxyTelemetryCollector();
2277
2559
  const resolverCollector = new ResolverTelemetryCollector({ redact });
2278
2560
  const nativeCollector = new NativeTelemetryCollector({ redact });
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 });
2279
2568
  const telemetry = new RequestTelemetry(trace);
2280
2569
  telemetry.register(proxyCollector);
2281
2570
  telemetry.register(resolverCollector);
2282
2571
  telemetry.register(nativeCollector);
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);
2283
2579
  let rootRunner: <T>(fn: () => Promise<T>) => Promise<T> = (fn) => fn();
2284
2580
  let resolveRoot!: (outcome: RequestTerminalOutcome) => void;
2285
2581
  const rootTerminal = new Promise<RequestTerminalOutcome>((resolve) => {
@@ -2362,6 +2658,13 @@ function createRequestScope(input: {
2362
2658
  telemetry,
2363
2659
  resolverTelemetry: resolverCollector,
2364
2660
  nativeTelemetry: nativeCollector,
2661
+ httpTelemetry: httpCollector,
2662
+ stealthTelemetry: stealthCollector,
2663
+ ocrTelemetry: ocrCollector,
2664
+ sttTelemetry: sttCollector,
2665
+ browserTelemetry: browserCollector,
2666
+ cacheTelemetry,
2667
+ stateTelemetry,
2365
2668
  seedCredentials(rawBody, kind): void {
2366
2669
  const harvested = rawRequestCredentials(rawBody, kind);
2367
2670
  sensitiveRegistry.add(harvested.values);
@@ -2581,21 +2884,13 @@ function toJsonSuccessResponse(
2581
2884
  }
2582
2885
 
2583
2886
  const cacheMeta = ctx && "cache" in ctx ? ctx.cache.responseMeta() : undefined;
2584
- const retryMeta = ctx ? retryResponseMeta.get(ctx) : undefined;
2887
+ const retry = ctx ? retryResponseMeta.get(ctx)?.toTenantRetryPayload() : undefined;
2585
2888
  type TenantCacheMeta = {
2586
2889
  hit: boolean;
2587
2890
  stale: boolean;
2588
2891
  keys: TenantOpaqueKeys;
2589
2892
  source?: ClosedEnum<NonNullable<ProviderCacheResponseMeta["source"]>>;
2590
2893
  };
2591
- type TenantRetryMeta = {
2592
- attempts: number;
2593
- retries: number;
2594
- preset?: ClosedEnum<NonNullable<HttpRetrySummary["preset"]>>;
2595
- transport: ClosedEnum<HttpRetrySummary["transport"]>;
2596
- lastErrorCode?: ClosedEnum<RetryLastErrorCode>;
2597
- lastStatus?: number;
2598
- };
2599
2894
  const cache: TenantNeutral<TenantCacheMeta> | undefined = cacheMeta
2600
2895
  ? {
2601
2896
  hit: cacheMeta.hit,
@@ -2605,22 +2900,8 @@ function toJsonSuccessResponse(
2605
2900
  ...(cacheMeta.source ? { source: closedEnum(cacheMeta.source) } : {}),
2606
2901
  }
2607
2902
  : undefined;
2608
- const retry: TenantNeutral<TenantRetryMeta> | undefined = retryMeta
2609
- ? {
2610
- attempts: retryMeta.attempts,
2611
- retries: retryMeta.retries,
2612
- ...(retryMeta.preset ? { preset: closedEnum(retryMeta.preset) } : {}),
2613
- transport: closedEnum(retryMeta.transport),
2614
- ...(retryMeta.lastErrorCode
2615
- ? {
2616
- lastErrorCode: closedEnum(tenantRetryLastErrorCode(retryMeta.lastErrorCode)),
2617
- }
2618
- : {}),
2619
- ...(retryMeta.lastStatus ? { lastStatus: retryMeta.lastStatus } : {}),
2620
- }
2621
- : undefined;
2622
2903
  const meta =
2623
- cacheMeta || retryMeta
2904
+ cacheMeta || retry
2624
2905
  ? {
2625
2906
  ...(cache
2626
2907
  ? {
@@ -2942,42 +3223,44 @@ function toAuthFlowResponse(
2942
3223
  };
2943
3224
  }
2944
3225
 
2945
- function authFlowLocaleFromHeaders(headers?: Record<string, string>): ProviderLocale {
2946
- const header = Object.entries(headers ?? {}).find(
2947
- ([key]) => key.toLowerCase() === "accept-language",
2948
- )?.[1];
2949
- for (const token of (header ?? "").split(",")) {
2950
- const language = token.trim().split(";")[0]?.split("-")[0]?.toLowerCase();
2951
- if (isAuthFlowLocale(language)) {
2952
- return language;
2953
- }
2954
- }
2955
- return "en";
2956
- }
2957
-
2958
- function isAuthFlowLocale(value: string | undefined): value is ProviderLocale {
2959
- return value === "en" || value === "ko" || value === "ja";
2960
- }
2961
-
2962
3226
  function isAuthTurn(value: unknown): value is AuthTurn {
2963
3227
  return !!value && typeof value === "object" && "kind" in value && "turnId" in value;
2964
3228
  }
2965
3229
 
2966
- 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(
2967
3241
  provider: ProviderDefinition,
3242
+ options: ProviderServerOptions,
3243
+ logger: ProviderServerLogger,
2968
3244
  ): ProviderLocaleCatalogMap | undefined {
3245
+ if (options.localeCatalogs) return options.localeCatalogs;
2969
3246
  for (const providerDir of [
2970
3247
  process.cwd(),
2971
3248
  join(process.cwd(), "providers", provider.id),
2972
3249
  join(process.cwd(), "providers-staging", provider.id),
2973
3250
  ]) {
2974
- 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;
2975
3255
  try {
2976
- return loadProviderLocaleCatalogs({
2977
- providerDir,
2978
- 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)),
2979
3263
  });
2980
- } catch {
2981
3264
  return undefined;
2982
3265
  }
2983
3266
  }
@@ -2985,16 +3268,12 @@ function loadAuthFlowLocaleCatalogs(
2985
3268
  }
2986
3269
 
2987
3270
  function materializeAuthFlowTurn(
2988
- provider: ProviderDefinition,
2989
- request: AuthFlowRequest,
2990
3271
  turn: AuthTurn,
3272
+ catalogs: ProviderLocaleCatalogMap | undefined,
3273
+ locale: ProviderLocale,
2991
3274
  ): AuthTurn {
2992
- const catalogs = loadAuthFlowLocaleCatalogs(provider);
2993
3275
  if (!catalogs) return turn;
2994
- return localizeAuthTurn(turn, {
2995
- catalogs,
2996
- locale: authFlowLocaleFromHeaders(request.headers),
2997
- });
3276
+ return localizeAuthTurn(turn, { catalogs, locale });
2998
3277
  }
2999
3278
 
3000
3279
  function withAuthRequestHeaders(request: AuthFlowRequest, headers: Headers): AuthFlowRequest {
@@ -3105,6 +3384,14 @@ async function handleAuthFlow(
3105
3384
  options: ProviderServerRuntimeOptions,
3106
3385
  state: ProviderRuntimeState,
3107
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,
3108
3395
  signal?: AbortSignal,
3109
3396
  ): Promise<Response | AuthFlowResponse> {
3110
3397
  const flow = provider.auth?.flow;
@@ -3161,7 +3448,7 @@ async function handleAuthFlow(
3161
3448
  !(result instanceof Response) &&
3162
3449
  !(result instanceof ReadableStream) &&
3163
3450
  isAuthTurn(result)
3164
- ? materializeAuthFlowTurn(provider, request, result)
3451
+ ? materializeAuthFlowTurn(result, options.localeCatalogs, locale)
3165
3452
  : result;
3166
3453
  return toAuthFlowResponse(materializedResult, getPatch(), getEngineState());
3167
3454
  } catch (error) {
@@ -3307,7 +3594,7 @@ function parseStatefulForwardingEnvelope(
3307
3594
  rawBody: unknown,
3308
3595
  redact: DiagnosticRedactor,
3309
3596
  ): ProviderServerStatefulForwardEnvelope {
3310
- const parsed = ProviderServerStatefulForwardEnvelopeSchema.safeParse(rawBody);
3597
+ const parsed = ReceivedStatefulForwardEnvelopeSchema.safeParse(rawBody);
3311
3598
  if (parsed.success) return parsed.data;
3312
3599
  throw new ProviderError("Stateful forwarding envelope is invalid.", {
3313
3600
  code: "STATEFUL_FORWARDING_ENVELOPE_INVALID",
@@ -3357,16 +3644,78 @@ export function createServerApp<TContext extends Partial<ProviderContext> = Prov
3357
3644
  );
3358
3645
  }
3359
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
+
3360
3698
  function createServerAppWithCapabilityModules(
3361
3699
  provider: ProviderDefinition,
3362
3700
  serverOptions: ProviderServerOptions,
3363
3701
  capabilityModules: ProviderCapabilityModules,
3364
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
+ );
3365
3712
  const options: ProviderServerRuntimeOptions = {
3366
3713
  ...serverOptions,
3714
+ ...(localeCatalogs ? { localeCatalogs } : {}),
3367
3715
  capabilityModules,
3368
- engine: serverOptions.engine ?? createInProcessProviderEngine(),
3716
+ engine: engineSelection.engine,
3369
3717
  };
3718
+ const engineAttached = !isUnavailableProviderEngine(options.engine);
3370
3719
  const app = new Hono();
3371
3720
  // Compile the startup inventory once, composed with the shared outside-context fallback.
3372
3721
  const staticSensitiveValues = compileProcessDiagnosticSensitiveValues(
@@ -3409,11 +3758,27 @@ function createServerAppWithCapabilityModules(
3409
3758
  const error = new ProviderError("Not found", { code: "not_found", retryable: false });
3410
3759
  const observabilityDetails = errorObservabilityDetails(error);
3411
3760
  return responseWithErrorObservability(
3412
- 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
+ ),
3413
3774
  observabilityDetails,
3414
3775
  );
3415
3776
  });
3416
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.
3417
3782
  app.get("/health", (c) =>
3418
3783
  c.json({
3419
3784
  status: "ok",
@@ -3422,6 +3787,25 @@ function createServerAppWithCapabilityModules(
3422
3787
  }),
3423
3788
  );
3424
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
+
3425
3809
  app.post(STATEFUL_INTERNAL_OPERATIONS_ROUTE, async (c) => {
3426
3810
  let rawBodyText = "";
3427
3811
  let rawBody: unknown;
@@ -3552,7 +3936,10 @@ function createServerAppWithCapabilityModules(
3552
3936
  requestId: request.requestId,
3553
3937
  operationId,
3554
3938
  headers: request.headers ?? {},
3555
- correlation: { connectionId: envelope.connectionId },
3939
+ correlation: {
3940
+ connectionId: envelope.connectionId,
3941
+ tenantId: resolveOperationTenantId(request),
3942
+ },
3556
3943
  });
3557
3944
  const response = await requestScope.run(async () => {
3558
3945
  const ctx = createProviderContext(
@@ -3597,7 +3984,18 @@ function createServerAppWithCapabilityModules(
3597
3984
  });
3598
3985
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
3599
3986
  const response = c.json(
3600
- 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
+ ),
3601
3999
  status,
3602
4000
  );
3603
4001
  return finalizeRequestResponse(requestScope, response, {
@@ -3634,7 +4032,10 @@ function createServerAppWithCapabilityModules(
3634
4032
  requestScope.enrich({
3635
4033
  requestId: body.requestId,
3636
4034
  headers: body.headers,
3637
- correlation: { connectionId: resolveOperationConnectionId(body) },
4035
+ correlation: {
4036
+ connectionId: resolveOperationConnectionId(body),
4037
+ tenantId: resolveOperationTenantId(body),
4038
+ },
3638
4039
  });
3639
4040
  const streaming = provider.operations[operation]?.transport?.kind
3640
4041
  ? provider.operations[operation]?.transport?.kind !== "json"
@@ -3675,7 +4076,18 @@ function createServerAppWithCapabilityModules(
3675
4076
  requestScope.enrich({ ...(requestId ? { requestId } : {}) });
3676
4077
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
3677
4078
  const response = c.json(
3678
- 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
+ ),
3679
4091
  status,
3680
4092
  );
3681
4093
  return finalizeRequestResponse(requestScope, response, {
@@ -3701,6 +4113,12 @@ function createServerAppWithCapabilityModules(
3701
4113
  for (const { path, flowRoute, logRoute } of authRoutes) {
3702
4114
  app.post(path, async (c) => {
3703
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
+ });
3704
4122
  const requestScope = createRequestScope({
3705
4123
  provider,
3706
4124
  staticSensitiveValues,
@@ -3715,6 +4133,11 @@ function createServerAppWithCapabilityModules(
3715
4133
  .clone()
3716
4134
  .json()
3717
4135
  .catch(() => undefined);
4136
+ localization = errorEnvelopeLocalization({
4137
+ catalogs: localeCatalogs,
4138
+ requestHeaders: c.req.raw.headers,
4139
+ rawBody,
4140
+ });
3718
4141
  requestScope.seedCredentials(rawBody, "auth");
3719
4142
  const body = withAuthRequestHeaders(
3720
4143
  AuthFlowRequestSchema.parse(rawBody),
@@ -3727,7 +4150,7 @@ function createServerAppWithCapabilityModules(
3727
4150
  correlation: {
3728
4151
  connectionId: resolveOperationConnectionId(body),
3729
4152
  flowId: body.flowId,
3730
- tenantId: body.tenantId,
4153
+ tenantId: normalizeIdentifier(body.tenantId),
3731
4154
  requestedProviderId:
3732
4155
  body.providerId !== undefined && body.providerId !== provider.id
3733
4156
  ? body.providerId
@@ -3742,6 +4165,7 @@ function createServerAppWithCapabilityModules(
3742
4165
  options,
3743
4166
  state,
3744
4167
  requestScope as RequestScope,
4168
+ localization.locale,
3745
4169
  c.req.raw.signal,
3746
4170
  );
3747
4171
  return handled instanceof Response ? handled : c.json(handled);
@@ -3756,7 +4180,13 @@ function createServerAppWithCapabilityModules(
3756
4180
  requestScope.enrich({ ...(requestId ? { requestId } : {}) });
3757
4181
  const observabilityDetails = errorObservabilityDetails(error);
3758
4182
  const response = c.json(
3759
- toErrorResponse(error, requestId, observabilityDetails, requestScope.redact),
4183
+ toErrorResponse(
4184
+ error,
4185
+ requestId,
4186
+ observabilityDetails,
4187
+ requestScope.redact,
4188
+ localization,
4189
+ ),
3760
4190
  status,
3761
4191
  );
3762
4192
  return finalizeRequestResponse(requestScope, response, {
@@ -3893,13 +4323,18 @@ export async function serve<TContext extends Partial<ProviderContext> = Provider
3893
4323
  );
3894
4324
  const configuredSignals = resolveShutdownSignals(options.shutdown?.signals ?? true);
3895
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);
3896
4330
  const serverAppOptions: ProviderServerOptions<TContext> = {
3897
4331
  logger: options.logger,
3898
- engine: options.engine,
4332
+ engine: engineSelection.engine,
3899
4333
  files: options.files,
3900
4334
  ocr: options.ocr,
3901
4335
  stt: options.stt,
3902
4336
  resolver: options.resolver,
4337
+ localeCatalogs: options.localeCatalogs,
3903
4338
  state: options.state,
3904
4339
  allowMemoryStateFallback: options.allowMemoryStateFallback,
3905
4340
  operationExecutor: options.operationExecutor,
@@ -3910,6 +4345,15 @@ export async function serve<TContext extends Partial<ProviderContext> = Provider
3910
4345
  createServerAppAsync(provider, serverAppOptions),
3911
4346
  selfTestSecrets ? import("./self-test.js") : undefined,
3912
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
+ );
3913
4357
 
3914
4358
  const servers: BunServerHandle[] = [];
3915
4359
  try {