@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
package/AUTHORING.md CHANGED
@@ -137,6 +137,73 @@ variable, the Cloudflare OCR/STT tokens and account identifier,
137
137
  `APIFUSE__OCR__API_KEY`, and `APIFUSE__CACHE__KEY_PEPPER`. `defineProvider`
138
138
  rejects them in `secrets`; the engine reads them from its own environment.
139
139
 
140
+ ### Engine mode
141
+
142
+ Provider processes attach their capability bindings through an engine. Today the
143
+ default is the in-process engine; ADR-0011 replaces it with the authenticated
144
+ remote engine in a phased migration, and `serve` logs every boot as
145
+ `provider_engine_mode` (`deprecated: true` for in-process). You do not need to
146
+ change anything now.
147
+
148
+ A deployment names the mode with the engine-owned `APIFUSE__ENGINE__MODE`
149
+ variable (`in-process` or `remote`; trimmed and case-folded, blank means unset);
150
+ hosts may pass `engineMode` to `serve`/`createServerApp`/`startDevServer`
151
+ instead. The manifest wins when the two disagree, so provider code cannot
152
+ silently downgrade what the deployment asked for; the disagreement is reported in
153
+ the boot event's `warnings`. An unrecognized value is warned about and ignored —
154
+ it never crashes the process. `APIFUSE__ENGINE__MODE` cannot be declared in
155
+ `secrets` (`APIFUSE__ENGINE__*` is engine-owned).
156
+
157
+ `remote` has no client in this release: the server attaches an engine that fails
158
+ every request with `PROVIDER_ENGINE_MODE_UNSUPPORTED` and reports `503` on
159
+ `/readyz`. It never falls back to in-process. `apifuse dev` and `apifuse record`
160
+ refuse to start instead.
161
+
162
+ `GET /health` is liveness only and stays engine-blind; `GET /readyz` reports the
163
+ resolved engine attachment and is what readiness/startup probes should use — but
164
+ only for a provider already pinned to an SDK release that serves the route.
165
+ Earlier pins answer `404`, and a failing readiness/startup probe is not
166
+ self-healing, so manifests move to `/readyz` after the pin wave, never before.
167
+
168
+ ### Deployment intent
169
+
170
+ Deployment intent (runtime profile, resources, HPA, Redis, extra TCP ports) has
171
+ exactly one authored home: the optional `deployment` key inside
172
+ `defineProvider({...})`. The SDK passes it through verbatim; the APIFuse
173
+ registry resolves omitted fields from the runtime profile and rejects anything
174
+ it cannot resolve.
175
+
176
+ - Declare only what differs from the profile. Profile defaults:
177
+ `runtime: "shared"`, `language: "typescript"`, `replicas: 1`,
178
+ `hpa: { enabled: true, minReplicas: 1, maxReplicas: 1, targetCPUUtilizationPercentage: 70 }`,
179
+ resources `25m/128Mi` (shared) or `200m/256Mi` (browser), `buildContext: "."`.
180
+ Most providers therefore declare no `deployment` key at all.
181
+ - The deployment `runtime` axis (`shared | dedicated | browser`) is distinct
182
+ from the execution `runtime` (`standard | shared | browser`) declared next to
183
+ it.
184
+ - Do not author a standalone `deploy.ts`, a `deployment` extra on a spread
185
+ default export (`export default { ...provider, deployment }`), or a
186
+ re-exported config module. A repository that still carries `deploy.ts`
187
+ migrates with `apifuse migrate-deployment .`: it hoists the file's non-default
188
+ values into the declaration, proves the result resolves identically, and
189
+ deletes the file. The `/deploy.ts` CODEOWNERS entry stays until the platform
190
+ stops honoring a re-added legacy file (`--drop-codeowners-lock` then). A
191
+ refusal names the manual step.
192
+ - `apifuse check` warns during the migration window
193
+ (`deployment/legacy-deploy-file`, `deployment/spread-export`,
194
+ `deployment/redundant-default`); the rules become errors once no repository
195
+ carries the legacy file.
196
+
197
+ ```ts
198
+ const buildProvider = defineProvider({
199
+ id: "example",
200
+ version: "1.0.0",
201
+ runtime: "standard",
202
+ deployment: { cache: { redis: { enabled: true } } },
203
+ // ...
204
+ });
205
+ ```
206
+
140
207
  ### Factored operations
141
208
 
142
209
  `defineProvider(declaration)` returns the builder that accepts `operations`, so
@@ -536,18 +603,20 @@ operation:
536
603
 
537
604
  ```ts
538
605
  errorCodes: [{
539
- code: "UPSTREAM_SCHEMA_ERROR",
540
- status: 502,
541
- retryable: true,
542
- description: "The upstream response no longer matches its schema.",
606
+ code: "ITEM_SOLD_OUT",
607
+ status: 409,
608
+ retryable: false,
609
+ description: "The upstream sold the item before checkout completed.",
543
610
  }],
544
611
  handler: async () => {
545
- throw new ProviderError("Upstream schema changed", {
546
- code: "UPSTREAM_SCHEMA_ERROR",
547
- });
612
+ throw new ProviderError("Item sold out", { code: "ITEM_SOLD_OUT" });
548
613
  },
549
614
  ```
550
615
 
616
+ Declare only codes the SDK does not already register. `UPSTREAM_SCHEMA_ERROR`,
617
+ `UPSTREAM_AUTH_ERROR`, and `INVALID_REQUEST` are registered below, so throwing
618
+ them needs no declaration at all.
619
+
551
620
  `defineProvider` accepts only statuses the server can emit: 400, 401, 404, 429,
552
621
  500, 502, 503, and 504. Invalid declared statuses fail provider definition,
553
622
  not a live request. Status selection uses this order:
@@ -567,10 +636,10 @@ The registered mappings are:
567
636
  | Error code or fallback | HTTP status |
568
637
  | --- | ---: |
569
638
  | `AUTH_REQUIRED`, `reauth_required` | 401 |
570
- | `MISSING_SECRET` | 400 |
639
+ | `MISSING_SECRET`, `UPSTREAM_AUTH_ERROR`, `INVALID_REQUEST` | 400 |
571
640
  | `NOT_FOUND`, `not_found`, `NO_DATA` | 404 |
572
641
  | `RATE_LIMITED`, `UPSTREAM_RATE_LIMIT`, `LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR` | 429 |
573
- | `UPSTREAM_ERROR`, `BLOCKED` | 502 |
642
+ | `UPSTREAM_ERROR`, `BLOCKED`, `UPSTREAM_SCHEMA_ERROR` | 502 |
574
643
  | `STT_UNAVAILABLE`, `UNSUPPORTED_STT_BACKEND`, `STATEFUL_FORWARDING_REPLAY_CACHE_FULL` | 503 |
575
644
  | Unregistered input `ValidationError` code | 400 |
576
645
  | Other unregistered `ProviderError` code | 500 |
@@ -587,6 +656,154 @@ Throw the domain `ProviderError` directly. Subclassing or wrapping it as a
587
656
  domain code's `status` instead. Genuine `TransportError` values remain
588
657
  SDK-owned and keep their 502/504 mapping.
589
658
 
659
+ #### Three fleet-consensus codes are registered
660
+
661
+ `UPSTREAM_AUTH_ERROR` (400), `UPSTREAM_SCHEMA_ERROR` (502), and
662
+ `INVALID_REQUEST` (400) are registered mappings. All three are non-retryable.
663
+
664
+ - `UPSTREAM_AUTH_ERROR` is 400, not 401 or 502, because it means the upstream
665
+ refused a *platform-managed* service key. The caller holds no credential, so
666
+ 401 ("re-authenticate") asks for something the caller cannot do, and 502
667
+ ("the upstream is sick") promises a recovery that will never arrive. It is a
668
+ deployment/config defect, exactly like `MISSING_SECRET`.
669
+ - `UPSTREAM_SCHEMA_ERROR` is non-retryable: a retry returns the same payload
670
+ the provider already could not normalize.
671
+ - `INVALID_REQUEST` is the spelling for caller-side bad input. `INVALID_INPUT`,
672
+ `VALIDATION_ERROR`, and `BAD_REQUEST` are not registered.
673
+
674
+ Like `UPSTREAM_ERROR` and `BLOCKED`, these are thrown by provider code rather
675
+ than by the SDK, so they are registered mappings and not SDK-owned codes: an
676
+ existing operation declaration still wins at runtime (step 2 above). Registering
677
+ them therefore never rewrites a status you already declared. What it does do is
678
+ let you delete the declaration — and when your declaration disagrees with the
679
+ registered mapping, `apifuse check` reports it rather than leaving the fleet
680
+ answering one code two ways:
681
+
682
+ - `error-code-status-conflicts-sdk` — declared `status` differs from the
683
+ registered status.
684
+ - `error-code-retryable-conflicts-sdk` — declared `retryable` differs from the
685
+ registered retryability.
686
+
687
+ Both are warnings. Drop the conflicting field so the SDK supplies the value, or,
688
+ if your operation genuinely means something else, throw a distinct code that
689
+ says so.
690
+
691
+ ### Localized error messages (`messageKey` / `fixKey`)
692
+
693
+ The positional `ProviderError` message stays **English**. It is what
694
+ `error.message`, cause frames, the `provider_request_failed` log, OTLP
695
+ attributes and tests see. Only two fields of the response body are localized:
696
+ `error.message` and `error.fix`. Nothing else — not `code`, not `details`, not
697
+ `errorCodes[].description`, not the `X-ApiFuse-Error-Observability` header.
698
+
699
+ Attach locale keys instead of parsing `Accept-Language` yourself:
700
+
701
+ ```ts
702
+ // locales/en.json
703
+ { "errors": { "upstreamSchema": {
704
+ "message": "{provider} returned an unexpected response.",
705
+ "fix": "Retry in a few minutes."
706
+ } } }
707
+ ```
708
+
709
+ ```ts
710
+ throw new ProviderError("The culture data service returned an unexpected response.", {
711
+ code: "UPSTREAM_SCHEMA_ERROR",
712
+ messageKey: "errors.upstreamSchema.message",
713
+ fixKey: "errors.upstreamSchema.fix",
714
+ params: { provider: "Culture Data" },
715
+ });
716
+ ```
717
+
718
+ Keys can also live on the declaration, which localizes **every** site that
719
+ throws that code without touching the throw sites:
720
+
721
+ ```ts
722
+ errorCodes: [{
723
+ code: "UPSTREAM_SCHEMA_ERROR",
724
+ description: "The upstream response no longer matches its schema.",
725
+ messageKey: "errors.upstreamSchema.message",
726
+ fixKey: "errors.upstreamSchema.fix",
727
+ }],
728
+ ```
729
+
730
+ `status` and `retryable` are omitted because `UPSTREAM_SCHEMA_ERROR` is a
731
+ registered code: the SDK already supplies 502 and non-retryable, and declaring
732
+ a different value trips `error-code-status-conflicts-sdk` /
733
+ `error-code-retryable-conflicts-sdk`. A declaration that carries only keys is
734
+ still worth writing — it localizes every site that throws the code.
735
+
736
+ Resolution order per field, evaluated at serve time:
737
+
738
+ 1. the throw site's `messageKey` / `fixKey`;
739
+ 2. the matching `errorCodes[]` entry's `messageKey` / `fixKey`;
740
+ 3. the derived `errors.<code>.message` / `errors.<code>.fix` (the code is used
741
+ verbatim as a catalog segment, so `errors.UPSTREAM_SCHEMA_ERROR.message`
742
+ works with no declaration at all);
743
+ 4. the English literal passed to the constructor.
744
+
745
+ Each candidate is resolved in the caller's locale first and then in `en`, so a
746
+ more specific key always beats a more specific locale. A missing key, a
747
+ malformed key, a non-string catalog value or an absent catalog is a miss, never
748
+ a throw: the caller always gets text. Steps 2 and 3 do not apply to SDK-owned
749
+ failures (transport, Zod, stateful deadline, SDK runtime codes) — a provider
750
+ catalog must not be able to relabel `Request timed out`.
751
+
752
+ **Key grammar.** A locale key is dot-separated segments. Every segment is
753
+ camelCase (`upstreamSchema`, `whenToUse`) or an array index (`0`), and the
754
+ first segment is always a camelCase namespace. The one exception is the segment
755
+ directly under `errors`, which may also be the error code spelled the way it is
756
+ thrown — `errors.UPSTREAM_SCHEMA_ERROR.message`, `errors.BLOCKED.fix`,
757
+ `errors.reauth_required.message`. Both spellings work, so
758
+ `errors.upstreamSchema.message` and `errors.UPSTREAM_SCHEMA_ERROR.message` are
759
+ equally legal keys; write the code form when you want the key to match the
760
+ derived path in step 3. Nothing else loosens: a space, a dot inside a segment,
761
+ an empty segment, a mixed-case or hyphenated code, and a leading digit are all
762
+ still malformed.
763
+
764
+ Locale negotiation reads `Accept-Language` from the request envelope's
765
+ `headers` map first (the gateway stamps the flow's locale there) and then from
766
+ the HTTP header, honours quality values, matches on the primary subtag, and
767
+ supports `en`, `ko`, `ja` with `en` as the default. A request with no
768
+ `Accept-Language` therefore keeps serving the `en` catalog value, so existing
769
+ contract fixtures do not churn.
770
+
771
+ **Interpolation.** `{name}` placeholders, `{{` and `}}` for literal braces.
772
+ `params` accepts strings and finite numbers only. A placeholder with no usable
773
+ param is left verbatim so the authoring gap is visible rather than silently
774
+ deleting text.
775
+
776
+ **Params are untrusted.** Upstream error text routinely ends up in `params`, so
777
+ every string value is scrubbed before substitution: secrets and e-mail
778
+ addresses redacted, control/bidi/newline characters encoded or collapsed,
779
+ `<`, `>` and backticks removed, HTML-entity ampersands (`&lt;`) neutralized
780
+ while ordinary ampersands survive, and the value capped at 200 characters.
781
+ Substituted text is never rescanned, so a value containing `{other}` cannot
782
+ trigger a second interpolation round. Prefer `details` when you need to relay
783
+ an upstream body verbatim; `params` is for short, bounded values.
784
+
785
+ **Catalog loading.** Catalogs are read once when the server is built (from the
786
+ provider directory's `locales/{en,ko,ja}.json`, loading only the locales that
787
+ exist). Pass `serve(provider, { localeCatalogs })` to bundle them instead. A
788
+ catalog that exists but cannot be parsed logs one
789
+ `provider_locale_catalogs_unavailable` warn at boot and every field falls back
790
+ to its English literal.
791
+
792
+ **Lint.** `apifuse check` reports:
793
+
794
+ - `error-locale-key-missing` / `error-locale-key-malformed` (**error**) — a
795
+ literal `messageKey`/`fixKey`, on a throw site or on an `errorCodes[]` entry,
796
+ that `locales/en.json` does not resolve to non-empty text, or that is not a
797
+ legal locale dot path (see **Key grammar** above).
798
+ - `thrown-error-message-not-localized` (**warn**) — a throw site whose declared
799
+ code has no key anywhere and no `errors.<code>.message` in `en`, so it is
800
+ served untranslated. Warning during the migration; promoted to error per
801
+ provider as each wave lands.
802
+
803
+ `ko`/`ja` coverage needs no new rule: `en` is the baseline, so the existing
804
+ catalog parity and placeholder checks already fail an `errors.*` key that is
805
+ missing or untranslated in `ko`/`ja`.
806
+
590
807
  ### Declared secrets are SDK-enforced
591
808
 
592
809
  Environment/secret presence validation is single-sourced in the SDK. Declare
@@ -678,6 +895,48 @@ redacted. If a login flow must consume a rotated credential from `Location`,
678
895
  inspect it inside `stopWhen`; that callback receives the real hop while callback
679
896
  failures are sanitized before propagation.
680
897
 
898
+ ### Request-bound proof headers
899
+
900
+ When an upstream requires a single-use, request-bound header (DPoP proofs,
901
+ HTTP message signatures, HMAC nonces), pass `headers` as a factory instead of a
902
+ record. `ctx.http` calls it once per issued attempt with the resolved URL and
903
+ method, so a managed retry sends a fresh proof rather than replaying the first
904
+ one, and the request keeps proxy failover and the transient retry taxonomy:
905
+
906
+ ```ts
907
+ const response = await ctx.http.get(requestUrl, {
908
+ headers: async ({ url, method }) => {
909
+ // RFC 9449 §4.2: `htu` is the target URI without query and fragment.
910
+ const htu = new URL(url);
911
+ htu.search = "";
912
+ htu.hash = "";
913
+ return {
914
+ ...commonHeaders,
915
+ dpop: await createDpopProof(htu.toString(), method),
916
+ };
917
+ },
918
+ });
919
+ ```
920
+
921
+ `url` is the exact URL the attempt is issued against, including `params` and
922
+ `sensitiveParams`. Bind to it instead of a URL you rebuild, but strip the query
923
+ and fragment for fields that are defined without them (DPoP `htu`), and never
924
+ log it or copy it verbatim into a signed field a verifier logs.
925
+
926
+ Keep the factory fast and local. It runs after proxy resolution, so an already
927
+ allocated egress endpoint and its lease are held while it is awaited; a remote
928
+ signing round trip there can outlive the lease TTL. Mint locally, and set
929
+ `timeout` on the call so a hung signer cannot hold the endpoint indefinitely.
930
+
931
+ The factory is not invoked for redirect hops (they reuse the attempt's
932
+ headers), for skipped duplicate proxy offsets, or for an attempt whose proxy
933
+ allocation failed — those build no request, so `attempt` counts only the
934
+ requests that are actually issued. A factory that throws fails the request with
935
+ the non-retryable `TransportError` code `http_header_factory_failed`, which is
936
+ classified as a provider-side fault (`provider_error`), not an upstream
937
+ failure. `ctx.stealth` has its own `headers` option and does not accept a
938
+ factory.
939
+
681
940
  ### Public local debugging checklist
682
941
 
683
942
  - Operation smoke requests use the provider server envelope:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.63
4
+
5
+ - Release candidate for main commit a9b85acc5af4c40ec59774b0d1e864e7ed21828e.
6
+
7
+ ## 2.2.0-beta.62
8
+
9
+ - Release candidate for main commit 98c623f799268b6d13ce59844b2b33ed78dcd0c3.
10
+
3
11
  ## 2.2.0-beta.61
4
12
 
5
13
  - Release candidate for main commit 8ba435285f6f448bb1b8d01b1d65d2e1a4b42aaa.
package/README.md CHANGED
@@ -78,6 +78,7 @@ The operation request body is the same envelope used by the APIFuse gateway:
78
78
  | `headers` | no | Extra caller headers to expose through `ctx.request.headers`. |
79
79
  | `connectionId` | no | Connection identity only; it does not include credentials. The gateway sends it for `optional` connection mode, and only when the caller supplied a connection that passed authorization. Exposed as `ctx.request.connectionId`. |
80
80
  | `connection` | no | Credential-bearing connection data. The gateway sends it for `required` connection mode; omit it for no-auth/public (`none` mode) operations. For local debugging, pass an object with `id`, `mode`, `secrets`, `metadata`, and `externalRef`. Do not pass `null`. |
81
+ | `tenantId` | no | Gateway-asserted principal scope: the organization id for customer subjects, the service-account id for platform service accounts. Sent for every connection mode, including `none`; absent on direct calls and self-test. Exposed as `ctx.request.tenantId` (`undefined` when absent or empty). Caller headers never populate it. |
81
82
 
82
83
  The gateway sends only `connection` for `required` mode, only `connectionId`
83
84
  for `optional` mode (and nothing when the caller passed no connection), and
@@ -15,6 +15,7 @@ import {
15
15
  DECLARATION_INVALID_CODE,
16
16
  validateFailClosedDeclaration,
17
17
  } from "../src/declaration-validation.js";
18
+ import { redundantDeploymentDefaults } from "../src/cli/deployment-intent.js";
18
19
  import { isProviderError } from "../src/errors.js";
19
20
  import type { ProviderDefinition } from "../src/index.js";
20
21
  import { lintProviderWithInformation, type ProviderLintMode } from "../src/lint.js";
@@ -140,6 +141,7 @@ export async function runChecks(
140
141
  }
141
142
  const provider = assertProviderDefinition(providerModule?.default);
142
143
  const providerSourceFiles = collectProviderSourceFiles(providerRoot);
144
+ const localeCatalogEn = readEnglishLocaleCatalog(providerRoot);
143
145
 
144
146
  return [
145
147
  checkIndex(indexPath, provider),
@@ -147,8 +149,9 @@ export async function runChecks(
147
149
  checkOperations(provider),
148
150
  checkFixtures(provider),
149
151
  checkSchemas(provider),
150
- checkAuthoringLint(provider, providerSourceFiles, options.lintMode),
152
+ checkAuthoringLint(provider, providerSourceFiles, localeCatalogEn, options.lintMode),
151
153
  checkProviderMetadata(provider),
154
+ await checkDeploymentIntent(providerRoot, provider),
152
155
  checkDockerfile(dockerfilePath),
153
156
  checkPackageJson(packageJsonPath),
154
157
  checkProviderJson(providerJsonPath, packageJsonPath),
@@ -367,9 +370,30 @@ function checkSchemas(provider: ProviderDefinition | undefined): CheckResult {
367
370
  };
368
371
  }
369
372
 
373
+ /**
374
+ * Reads `locales/en.json` so the error-localization lint rules can resolve
375
+ * declared `messageKey`/`fixKey` values. An absent or unreadable catalog is
376
+ * reported as an empty catalog rather than as "no catalog": a provider that
377
+ * references locale keys without shipping `en.json` must still fail the
378
+ * missing-key rule instead of silently skipping it.
379
+ */
380
+ function readEnglishLocaleCatalog(providerRoot: string): Record<string, unknown> {
381
+ const catalogPath = resolve(providerRoot, "locales", "en.json");
382
+ if (!existsSync(catalogPath)) return {};
383
+ try {
384
+ const parsed: unknown = JSON.parse(readFileSync(catalogPath, "utf8"));
385
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
386
+ ? (parsed as Record<string, unknown>)
387
+ : {};
388
+ } catch {
389
+ return {};
390
+ }
391
+ }
392
+
370
393
  function checkAuthoringLint(
371
394
  provider: ProviderDefinition | undefined,
372
395
  providerSourceFiles: Record<string, string>,
396
+ localeCatalogEn: Record<string, unknown>,
373
397
  lintMode: ProviderLintMode = "official",
374
398
  ): CheckResult {
375
399
  if (!provider) {
@@ -380,7 +404,7 @@ function checkAuthoringLint(
380
404
  }
381
405
 
382
406
  const { diagnostics, information } = lintProviderWithInformation(
383
- { ...provider, providerSourceFiles },
407
+ { ...provider, providerSourceFiles, localeCatalogEn },
384
408
  { mode: lintMode },
385
409
  );
386
410
  const errors = diagnostics.filter((diagnostic) => diagnostic.level === "error");
@@ -448,6 +472,64 @@ function checkProviderMetadata(provider: ProviderDefinition | undefined): CheckR
448
472
  };
449
473
  }
450
474
 
475
+ export const DEPLOYMENT_INTENT_CHECK_MESSAGE =
476
+ "Deployment intent lives only in defineProvider({ deployment }) without restating profile defaults";
477
+ export const LEGACY_DEPLOY_FILE_RULE = "deployment/legacy-deploy-file";
478
+ export const REDUNDANT_DEPLOYMENT_DEFAULT_RULE = "deployment/redundant-default";
479
+ export const SPREAD_EXPORT_DEPLOYMENT_RULE = "deployment/spread-export";
480
+
481
+ /**
482
+ * Deployment intent has one authored home: the `deployment` key on
483
+ * `defineProvider()`, carrying only the fields that differ from the runtime
484
+ * profile. A standalone `deploy.ts` is a legacy surface and a field that
485
+ * restates a profile default is noise the platform derives anyway. Both are
486
+ * reported as warnings while the fleet migrates (`apifuse
487
+ * migrate-deployment`); they become errors once no repository carries the
488
+ * legacy file.
489
+ */
490
+ async function checkDeploymentIntent(
491
+ providerRoot: string,
492
+ provider: ProviderDefinition | undefined,
493
+ ): Promise<CheckResult> {
494
+ const details: string[] = [];
495
+ if (existsSync(resolve(providerRoot, "deploy.ts"))) {
496
+ details.push(
497
+ `WARN ${LEGACY_DEPLOY_FILE_RULE}: deploy.ts is a legacy deployment surface; run \`apifuse migrate-deployment .\` to move its non-default values into defineProvider({ deployment }) and delete the file.`,
498
+ );
499
+ }
500
+ // A `deployment` extra on `export default { ...provider, deployment }` is
501
+ // indistinguishable from a declaration key once the module is loaded, so
502
+ // inspect the source. The inspector needs the TypeScript compiler (a
503
+ // devDependency of provider repositories); without it this rule is skipped.
504
+ const indexPath = resolve(providerRoot, "index.ts");
505
+ if (existsSync(indexPath)) {
506
+ try {
507
+ const { findSpreadExportDeploymentProperty } = await import(
508
+ "../src/cli/migrate-deployment-intent.js"
509
+ );
510
+ const spread = findSpreadExportDeploymentProperty(readFileSync(indexPath, "utf8"), indexPath);
511
+ if (spread !== undefined) {
512
+ details.push(
513
+ `WARN ${SPREAD_EXPORT_DEPLOYMENT_RULE}: index.ts:${spread.line} attaches \`deployment\` to a spread default export; the only authored surface is the \`deployment\` key inside defineProvider({...}). Run \`apifuse migrate-deployment .\` to hoist it.`,
514
+ );
515
+ }
516
+ } catch {
517
+ // TypeScript unavailable: the source-shape rule cannot run here.
518
+ }
519
+ }
520
+ if (provider?.deployment !== undefined) {
521
+ for (const redundant of redundantDeploymentDefaults(provider.deployment)) {
522
+ details.push(`WARN ${REDUNDANT_DEPLOYMENT_DEFAULT_RULE}: ${redundant.message}`);
523
+ }
524
+ }
525
+ return {
526
+ message: DEPLOYMENT_INTENT_CHECK_MESSAGE,
527
+ // Warning-only during the fleet migration window.
528
+ passed: true,
529
+ details,
530
+ };
531
+ }
532
+
451
533
  function checkDockerfile(dockerfilePath: string): CheckResult {
452
534
  return {
453
535
  message: "Dockerfile exists",
@@ -11,20 +11,18 @@ import {
11
11
  createProviderCache,
12
12
  createHandleContext,
13
13
  createProviderEnvironment,
14
- createUnsupportedResolverClient,
15
14
  createSttClientFromEnv,
16
- readEngineProxyCredentials,
17
15
  type ProviderDefinition,
18
16
  type ProviderEngineBindingCandidates,
19
17
  ProviderError,
20
- type ProviderProxyPolicy,
21
18
  } from "../src/index.js";
19
+ import { createCliResolverRuntime } from "../src/cli/resolver-runtime.js";
20
+ import { assertProcessEngineModeSupported } from "../src/runtime/engine-mode.js";
22
21
  import { createBrowserClient } from "../src/runtime/browser.js";
23
- import { createResolverClientFromEnv } from "../src/runtime/resolver.js";
24
22
  import { createMemoryProviderRuntimeState } from "../src/runtime/state.js";
25
23
  import { createStealthClient } from "../src/runtime/stealth.js";
24
+ import type { ResolverTelemetryCollector } from "../src/runtime/resolver-telemetry.js";
26
25
  import { createTraceContext } from "../src/runtime/trace.js";
27
- import { getStealthProfile } from "../src/stealth/profiles.js";
28
26
  import type { BrowserClient, ProviderContext } from "../src/types.js";
29
27
 
30
28
  const HELP_TEXT = `Usage: apifuse dev [path]
@@ -43,6 +41,9 @@ export async function main() {
43
41
  const providerModule = await import(resolve(providerPath, "index.ts"));
44
42
  const provider = assertProviderDefinition(providerModule.default, providerPath);
45
43
 
44
+ // Fail fast before the server starts: this is a developer tool, and an
45
+ // explicit remote request must never silently fall back to in-process.
46
+ assertProcessEngineModeSupported();
46
47
  const { startDevServer } = await import("../src/dev.js");
47
48
  const port = Number(process.env.APIFUSE__RUNTIME__PORT) || 3900;
48
49
 
@@ -50,6 +51,7 @@ export async function main() {
50
51
 
51
52
  console.log("\nEndpoints:");
52
53
  console.log(` GET http://localhost:${port}/health`);
54
+ console.log(` GET http://localhost:${port}/readyz`);
53
55
 
54
56
  for (const operationId of Object.keys(provider.operations)) {
55
57
  console.log(` POST http://localhost:${port}/v1/${operationId}`);
@@ -81,18 +83,17 @@ export async function main() {
81
83
 
82
84
  export function createProviderContext(provider: ProviderDefinition): {
83
85
  ctx: ProviderContext;
86
+ resolverTelemetry: ResolverTelemetryCollector;
84
87
  } {
85
88
  const providerEnvironment = createProviderEnvironment(
86
89
  process.env,
87
90
  provider.secrets?.map((secret) => secret.name) ?? [],
88
91
  );
89
92
  const providerEnv = { get: (key: string) => readDiagnosticEnv(key, providerEnvironment) };
90
- const engineCredentials = readEngineProxyCredentials();
91
93
  const credential = createCredentialContext();
92
94
  const state = createMemoryProviderRuntimeState();
93
95
  const cache = createProviderCache({ providerId: provider.id });
94
- const proxyPolicy = resolveNativeProxyPolicy(provider);
95
- const stealthProfile = provider.stealth ? getStealthProfile(provider.stealth) : undefined;
96
+ const { resolver, resolverTelemetry } = createCliResolverRuntime(provider, cache);
96
97
  const candidates: ProviderEngineBindingCandidates = {
97
98
  env: providerEnv,
98
99
  credential,
@@ -113,21 +114,7 @@ export function createProviderContext(provider: ProviderDefinition): {
113
114
  }),
114
115
  ocr: createOcrClientFromEnv(provider.ocr),
115
116
  stt: createSttClientFromEnv(provider.stt),
116
- resolver: provider.resolver
117
- ? createResolverClientFromEnv(provider.resolver, engineCredentials, {
118
- allowedHosts: provider.allowedHosts,
119
- cache,
120
- ...(proxyPolicy
121
- ? {
122
- proxyIntent: {
123
- mode: proxyPolicy.mode,
124
- upstream: { proxy: provider.proxy },
125
- ...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
126
- },
127
- }
128
- : {}),
129
- })
130
- : createUnsupportedResolverClient("Provider does not declare resolver capability"),
117
+ resolver,
131
118
  handle: createHandleContext({
132
119
  providerId: provider.id,
133
120
  // Local dev is a single-user session: one stable connection scope so
@@ -141,14 +128,7 @@ export function createProviderContext(provider: ProviderDefinition): {
141
128
  bindings: candidates,
142
129
  }) as ProviderContext;
143
130
 
144
- return { ctx };
145
- }
146
-
147
- function resolveNativeProxyPolicy(provider: ProviderDefinition): ProviderProxyPolicy | undefined {
148
- if (typeof provider.proxy === "object") return provider.proxy;
149
- if (provider.proxy === true) return { mode: "optional" };
150
- if (provider.proxy === false) return { mode: "disabled" };
151
- return undefined;
131
+ return { ctx, resolverTelemetry };
152
132
  }
153
133
 
154
134
  function normalizeArgs(argv: string[]): string[] {