@wrenai/genbi 0.0.1

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 (286) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +141 -0
  3. package/bin/genbi.mjs +31 -0
  4. package/dist/assets/echarts-LTFC-ysd.js +40 -0
  5. package/dist/assets/index-Bo--QMUN.js +441 -0
  6. package/dist/assets/index-Xs4q7pNo.css +1 -0
  7. package/dist/index.html +14 -0
  8. package/dist/wrenai_logo.png +0 -0
  9. package/dist/wrenai_logo_white.png +0 -0
  10. package/dist-server/harness/auth/detect.d.ts +18 -0
  11. package/dist-server/harness/auth/detect.js +37 -0
  12. package/dist-server/harness/auth/index.d.ts +4 -0
  13. package/dist-server/harness/auth/index.js +2 -0
  14. package/dist-server/harness/auth/probe.d.ts +18 -0
  15. package/dist-server/harness/auth/probe.js +52 -0
  16. package/dist-server/harness/auth/types.d.ts +42 -0
  17. package/dist-server/harness/auth/types.js +9 -0
  18. package/dist-server/harness/bundle/loader.d.ts +32 -0
  19. package/dist-server/harness/bundle/loader.js +43 -0
  20. package/dist-server/harness/bundle/schema.d.ts +199 -0
  21. package/dist-server/harness/bundle/schema.js +127 -0
  22. package/dist-server/harness/bundle/version.d.ts +10 -0
  23. package/dist-server/harness/bundle/version.js +43 -0
  24. package/dist-server/harness/capability/gate.d.ts +6 -0
  25. package/dist-server/harness/capability/gate.js +29 -0
  26. package/dist-server/harness/capability/registry.d.ts +6 -0
  27. package/dist-server/harness/capability/registry.js +17 -0
  28. package/dist-server/harness/cli-args.d.ts +123 -0
  29. package/dist-server/harness/cli-args.js +247 -0
  30. package/dist-server/harness/cli.d.ts +2 -0
  31. package/dist-server/harness/cli.js +151 -0
  32. package/dist-server/harness/compile/cache.d.ts +23 -0
  33. package/dist-server/harness/compile/cache.js +109 -0
  34. package/dist-server/harness/compile/compose-profile.d.ts +34 -0
  35. package/dist-server/harness/compile/compose-profile.js +95 -0
  36. package/dist-server/harness/compile/errors.d.ts +22 -0
  37. package/dist-server/harness/compile/errors.js +41 -0
  38. package/dist-server/harness/compile/fingerprint.d.ts +22 -0
  39. package/dist-server/harness/compile/fingerprint.js +84 -0
  40. package/dist-server/harness/compile/index.d.ts +8 -0
  41. package/dist-server/harness/compile/index.js +7 -0
  42. package/dist-server/harness/compile/pipeline.d.ts +66 -0
  43. package/dist-server/harness/compile/pipeline.js +176 -0
  44. package/dist-server/harness/compile/resolve-binary.d.ts +55 -0
  45. package/dist-server/harness/compile/resolve-binary.js +133 -0
  46. package/dist-server/harness/compile/types.d.ts +107 -0
  47. package/dist-server/harness/compile/types.js +1 -0
  48. package/dist-server/harness/compile/warble-identity.d.ts +12 -0
  49. package/dist-server/harness/compile/warble-identity.js +33 -0
  50. package/dist-server/harness/compliance/gate.d.ts +35 -0
  51. package/dist-server/harness/compliance/gate.js +48 -0
  52. package/dist-server/harness/compliance/index.d.ts +2 -0
  53. package/dist-server/harness/compliance/index.js +1 -0
  54. package/dist-server/harness/compliance/types.d.ts +16 -0
  55. package/dist-server/harness/compliance/types.js +1 -0
  56. package/dist-server/harness/events/emitter.d.ts +34 -0
  57. package/dist-server/harness/events/emitter.js +25 -0
  58. package/dist-server/harness/events/index.d.ts +4 -0
  59. package/dist-server/harness/events/index.js +2 -0
  60. package/dist-server/harness/events/sse.d.ts +20 -0
  61. package/dist-server/harness/events/sse.js +48 -0
  62. package/dist-server/harness/events/types.d.ts +131 -0
  63. package/dist-server/harness/events/types.js +1 -0
  64. package/dist-server/harness/exec/errors.d.ts +16 -0
  65. package/dist-server/harness/exec/errors.js +29 -0
  66. package/dist-server/harness/exec/index.d.ts +5 -0
  67. package/dist-server/harness/exec/index.js +2 -0
  68. package/dist-server/harness/exec/local.d.ts +32 -0
  69. package/dist-server/harness/exec/local.js +134 -0
  70. package/dist-server/harness/exec/policy.d.ts +12 -0
  71. package/dist-server/harness/exec/policy.js +1 -0
  72. package/dist-server/harness/exec/types.d.ts +63 -0
  73. package/dist-server/harness/exec/types.js +1 -0
  74. package/dist-server/harness/guardrails/index.d.ts +2 -0
  75. package/dist-server/harness/guardrails/index.js +1 -0
  76. package/dist-server/harness/guardrails/policy.d.ts +45 -0
  77. package/dist-server/harness/guardrails/policy.js +57 -0
  78. package/dist-server/harness/index.d.ts +37 -0
  79. package/dist-server/harness/index.js +19 -0
  80. package/dist-server/harness/loop/errors.d.ts +53 -0
  81. package/dist-server/harness/loop/errors.js +72 -0
  82. package/dist-server/harness/loop/executor.d.ts +123 -0
  83. package/dist-server/harness/loop/executor.js +380 -0
  84. package/dist-server/harness/loop/guard.d.ts +16 -0
  85. package/dist-server/harness/loop/guard.js +1 -0
  86. package/dist-server/harness/loop/index.d.ts +5 -0
  87. package/dist-server/harness/loop/index.js +3 -0
  88. package/dist-server/harness/native-producer/index.d.ts +109 -0
  89. package/dist-server/harness/native-producer/index.js +302 -0
  90. package/dist-server/harness/npm-package-resolve.d.ts +28 -0
  91. package/dist-server/harness/npm-package-resolve.js +47 -0
  92. package/dist-server/harness/providers/adapters/anthropic.d.ts +7 -0
  93. package/dist-server/harness/providers/adapters/anthropic.js +6 -0
  94. package/dist-server/harness/providers/adapters/mock.d.ts +4 -0
  95. package/dist-server/harness/providers/adapters/mock.js +5 -0
  96. package/dist-server/harness/providers/adapters/openai-compatible.d.ts +10 -0
  97. package/dist-server/harness/providers/adapters/openai-compatible.js +10 -0
  98. package/dist-server/harness/providers/binding.d.ts +23 -0
  99. package/dist-server/harness/providers/binding.js +13 -0
  100. package/dist-server/harness/providers/errors.d.ts +6 -0
  101. package/dist-server/harness/providers/errors.js +12 -0
  102. package/dist-server/harness/providers/index.d.ts +18 -0
  103. package/dist-server/harness/providers/index.js +22 -0
  104. package/dist-server/harness/providers/registry.d.ts +16 -0
  105. package/dist-server/harness/providers/registry.js +24 -0
  106. package/dist-server/harness/render/envelope.d.ts +145 -0
  107. package/dist-server/harness/render/envelope.js +428 -0
  108. package/dist-server/harness/render/errors.d.ts +33 -0
  109. package/dist-server/harness/render/errors.js +46 -0
  110. package/dist-server/harness/render/index.d.ts +5 -0
  111. package/dist-server/harness/render/index.js +3 -0
  112. package/dist-server/harness/render/validate.d.ts +13 -0
  113. package/dist-server/harness/render/validate.js +88 -0
  114. package/dist-server/harness/route/adapter-spec.d.ts +40 -0
  115. package/dist-server/harness/route/adapter-spec.js +61 -0
  116. package/dist-server/harness/route/agent-sdk-cli.d.ts +30 -0
  117. package/dist-server/harness/route/agent-sdk-cli.js +86 -0
  118. package/dist-server/harness/route/agent-sdk-manifest.d.ts +31 -0
  119. package/dist-server/harness/route/agent-sdk-manifest.js +56 -0
  120. package/dist-server/harness/route/artifact-content.d.ts +53 -0
  121. package/dist-server/harness/route/artifact-content.js +83 -0
  122. package/dist-server/harness/route/chat-event-mapper.d.ts +95 -0
  123. package/dist-server/harness/route/chat-event-mapper.js +164 -0
  124. package/dist-server/harness/route/codex-ask-events.d.ts +25 -0
  125. package/dist-server/harness/route/codex-ask-events.js +395 -0
  126. package/dist-server/harness/route/codex-ask.d.ts +15 -0
  127. package/dist-server/harness/route/codex-ask.js +242 -0
  128. package/dist-server/harness/route/codex-local-cli.d.ts +20 -0
  129. package/dist-server/harness/route/codex-local-cli.js +78 -0
  130. package/dist-server/harness/route/codex-local-manifest.d.ts +22 -0
  131. package/dist-server/harness/route/codex-local-manifest.js +180 -0
  132. package/dist-server/harness/route/describe.d.ts +54 -0
  133. package/dist-server/harness/route/describe.js +84 -0
  134. package/dist-server/harness/route/dispatched.d.ts +138 -0
  135. package/dist-server/harness/route/dispatched.js +339 -0
  136. package/dist-server/harness/route/in-process.d.ts +43 -0
  137. package/dist-server/harness/route/in-process.js +106 -0
  138. package/dist-server/harness/route/index.d.ts +19 -0
  139. package/dist-server/harness/route/index.js +12 -0
  140. package/dist-server/harness/route/profile-source.d.ts +37 -0
  141. package/dist-server/harness/route/profile-source.js +76 -0
  142. package/dist-server/harness/route/route.d.ts +33 -0
  143. package/dist-server/harness/route/route.js +114 -0
  144. package/dist-server/harness/route/tier-binding.d.ts +36 -0
  145. package/dist-server/harness/route/tier-binding.js +63 -0
  146. package/dist-server/harness/route/types.d.ts +286 -0
  147. package/dist-server/harness/route/types.js +1 -0
  148. package/dist-server/harness/session/errors.d.ts +4 -0
  149. package/dist-server/harness/session/errors.js +7 -0
  150. package/dist-server/harness/session/gate.d.ts +11 -0
  151. package/dist-server/harness/session/gate.js +13 -0
  152. package/dist-server/harness/session/index.d.ts +5 -0
  153. package/dist-server/harness/session/index.js +3 -0
  154. package/dist-server/harness/session/run.d.ts +60 -0
  155. package/dist-server/harness/session/run.js +333 -0
  156. package/dist-server/harness/session/types.d.ts +29 -0
  157. package/dist-server/harness/session/types.js +1 -0
  158. package/dist-server/harness/setup/codex-events.d.ts +12 -0
  159. package/dist-server/harness/setup/codex-events.js +108 -0
  160. package/dist-server/harness/setup/runner.d.ts +497 -0
  161. package/dist-server/harness/setup/runner.js +1314 -0
  162. package/dist-server/harness/tools/errors.d.ts +107 -0
  163. package/dist-server/harness/tools/errors.js +141 -0
  164. package/dist-server/harness/tools/index.d.ts +13 -0
  165. package/dist-server/harness/tools/index.js +8 -0
  166. package/dist-server/harness/tools/mcp.d.ts +26 -0
  167. package/dist-server/harness/tools/mcp.js +15 -0
  168. package/dist-server/harness/tools/native.d.ts +179 -0
  169. package/dist-server/harness/tools/native.js +322 -0
  170. package/dist-server/harness/tools/parse.d.ts +14 -0
  171. package/dist-server/harness/tools/parse.js +18 -0
  172. package/dist-server/harness/tools/resolve-wren-binary.d.ts +12 -0
  173. package/dist-server/harness/tools/resolve-wren-binary.js +21 -0
  174. package/dist-server/harness/tools/resolve.d.ts +51 -0
  175. package/dist-server/harness/tools/resolve.js +79 -0
  176. package/dist-server/harness/tools/setup-native.d.ts +84 -0
  177. package/dist-server/harness/tools/setup-native.js +353 -0
  178. package/dist-server/harness/tools/wiring.d.ts +10 -0
  179. package/dist-server/harness/tools/wiring.js +16 -0
  180. package/dist-server/providers/setup.provider.yaml +31 -0
  181. package/dist-server/providers/wren.provider.yaml +33 -0
  182. package/dist-server/server/adopt.d.ts +87 -0
  183. package/dist-server/server/adopt.js +263 -0
  184. package/dist-server/server/app.d.ts +6 -0
  185. package/dist-server/server/app.js +2521 -0
  186. package/dist-server/server/auth-choice.d.ts +16 -0
  187. package/dist-server/server/auth-choice.js +36 -0
  188. package/dist-server/server/bin.d.ts +2 -0
  189. package/dist-server/server/bin.js +601 -0
  190. package/dist-server/server/bootstrap-project-binding.d.ts +31 -0
  191. package/dist-server/server/bootstrap-project-binding.js +71 -0
  192. package/dist-server/server/clarify.d.ts +10 -0
  193. package/dist-server/server/clarify.js +17 -0
  194. package/dist-server/server/codex-setup-mcp.d.ts +2 -0
  195. package/dist-server/server/codex-setup-mcp.js +147 -0
  196. package/dist-server/server/compose.d.ts +89 -0
  197. package/dist-server/server/compose.js +271 -0
  198. package/dist-server/server/conn-config.d.ts +30 -0
  199. package/dist-server/server/conn-config.js +253 -0
  200. package/dist-server/server/context-files.d.ts +12 -0
  201. package/dist-server/server/context-files.js +117 -0
  202. package/dist-server/server/context-map.d.ts +30 -0
  203. package/dist-server/server/context-map.js +95 -0
  204. package/dist-server/server/context-source.d.ts +68 -0
  205. package/dist-server/server/context-source.js +85 -0
  206. package/dist-server/server/db.d.ts +862 -0
  207. package/dist-server/server/db.js +1839 -0
  208. package/dist-server/server/enrichment-runner.d.ts +184 -0
  209. package/dist-server/server/enrichment-runner.js +213 -0
  210. package/dist-server/server/enrichment-verify.d.ts +73 -0
  211. package/dist-server/server/enrichment-verify.js +290 -0
  212. package/dist-server/server/enrichment.d.ts +232 -0
  213. package/dist-server/server/enrichment.js +199 -0
  214. package/dist-server/server/env-detect.d.ts +8 -0
  215. package/dist-server/server/env-detect.js +17 -0
  216. package/dist-server/server/env-field-hints.d.ts +46 -0
  217. package/dist-server/server/env-field-hints.js +48 -0
  218. package/dist-server/server/fold.d.ts +133 -0
  219. package/dist-server/server/fold.js +407 -0
  220. package/dist-server/server/harness-profile-sources.d.ts +3 -0
  221. package/dist-server/server/harness-profile-sources.js +24 -0
  222. package/dist-server/server/harness.d.ts +19 -0
  223. package/dist-server/server/harness.js +439 -0
  224. package/dist-server/server/impact.d.ts +18 -0
  225. package/dist-server/server/impact.js +85 -0
  226. package/dist-server/server/interactive-terminal.d.ts +175 -0
  227. package/dist-server/server/interactive-terminal.js +336 -0
  228. package/dist-server/server/native-artifacts.d.ts +1004 -0
  229. package/dist-server/server/native-artifacts.js +564 -0
  230. package/dist-server/server/native-dispatch-registry.d.ts +42 -0
  231. package/dist-server/server/native-dispatch-registry.js +45 -0
  232. package/dist-server/server/native-producer-fence.d.ts +14 -0
  233. package/dist-server/server/native-producer-fence.js +48 -0
  234. package/dist-server/server/native-session-resume.d.ts +6 -0
  235. package/dist-server/server/native-session-resume.js +103 -0
  236. package/dist-server/server/native-session-workspace.d.ts +40 -0
  237. package/dist-server/server/native-session-workspace.js +175 -0
  238. package/dist-server/server/native-sessions.d.ts +374 -0
  239. package/dist-server/server/native-sessions.js +1517 -0
  240. package/dist-server/server/native-wren-runtime.d.ts +20 -0
  241. package/dist-server/server/native-wren-runtime.js +259 -0
  242. package/dist-server/server/node-pty-host.d.ts +31 -0
  243. package/dist-server/server/node-pty-host.js +100 -0
  244. package/dist-server/server/route-intent.d.ts +40 -0
  245. package/dist-server/server/route-intent.js +23 -0
  246. package/dist-server/server/runtime-binding.d.ts +52 -0
  247. package/dist-server/server/runtime-binding.js +258 -0
  248. package/dist-server/server/source-catalog.d.ts +84 -0
  249. package/dist-server/server/source-catalog.js +221 -0
  250. package/dist-server/server/source-driver.d.ts +35 -0
  251. package/dist-server/server/source-driver.js +124 -0
  252. package/dist-server/server/spa.d.ts +2 -0
  253. package/dist-server/server/spa.js +51 -0
  254. package/dist-server/server/subscription-model-catalog.d.ts +27 -0
  255. package/dist-server/server/subscription-model-catalog.js +203 -0
  256. package/dist-server/server/turn.d.ts +249 -0
  257. package/dist-server/server/turn.js +1074 -0
  258. package/dist-server/server/wire-types.d.ts +799 -0
  259. package/dist-server/server/wire-types.js +1 -0
  260. package/dist-server/server/wren-home.d.ts +19 -0
  261. package/dist-server/server/wren-home.js +33 -0
  262. package/dist-server/server/wren-profiles.d.ts +43 -0
  263. package/dist-server/server/wren-profiles.js +123 -0
  264. package/package.json +93 -0
  265. package/profiles/genbi-default/context/binding.yml +9 -0
  266. package/profiles/genbi-default/ir.golden.json +506 -0
  267. package/profiles/genbi-default/profile.yml +16 -0
  268. package/profiles/genbi-enrich-context/components/apply_enrichment/component.yml +42 -0
  269. package/profiles/genbi-enrich-context/components/draft_enrichment/component.yml +30 -0
  270. package/profiles/genbi-enrich-context/components/draft_enrichment/steps/draft.md +34 -0
  271. package/profiles/genbi-enrich-context/components/inspect_context/component.yml +32 -0
  272. package/profiles/genbi-enrich-context/components/inspect_context/steps/inspect.md +13 -0
  273. package/profiles/genbi-enrich-context/context/binding.yml +4 -0
  274. package/profiles/genbi-enrich-context/ir.golden.json +302 -0
  275. package/profiles/genbi-enrich-context/profile.yml +15 -0
  276. package/profiles/genbi-monitor/context/binding.yml +7 -0
  277. package/profiles/genbi-monitor/ir.golden.json +188 -0
  278. package/profiles/genbi-monitor/profile.yml +27 -0
  279. package/profiles/genbi-setup/components/build_context/component.yml +44 -0
  280. package/profiles/genbi-setup/components/build_context/steps/build.md +14 -0
  281. package/profiles/genbi-setup/components/connect_source/component.yml +51 -0
  282. package/profiles/genbi-setup/components/connect_source/steps/connect.md +23 -0
  283. package/profiles/genbi-setup/context/binding.yml +8 -0
  284. package/profiles/genbi-setup/ir.golden.json +140 -0
  285. package/profiles/genbi-setup/profile.yml +18 -0
  286. package/profiles/genbi-setup/raw/schema.json +5 -0
@@ -0,0 +1,106 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { createDefaultCapabilityRegistry } from "../capability/registry.js";
4
+ import { createLocalExecutionEnv } from "../exec/index.js";
5
+ import { deriveEnforcement } from "../guardrails/index.js";
6
+ import { createDefaultProviderRegistry } from "../providers/index.js";
7
+ import { runAgent } from "../session/index.js";
8
+ import { createWrenNativeToolRegistry, resolveWrenBinary } from "../tools/index.js";
9
+ import { deriveAdapterSpec } from "./adapter-spec.js";
10
+ import { describeBundle } from "./describe.js";
11
+ import { buildHybridTierBinding, buildUniformTierBinding } from "./tier-binding.js";
12
+ const ANSWER_QUERY_AGENT_ID = "answer_query";
13
+ /** Operator-wide override for `resolveArtifactsDir`'s default, e.g. for a long-running BFF process. */
14
+ const ARTIFACTS_DIR_ENV_VAR = "WREN_HARNESS_ARTIFACTS_DIR";
15
+ /** Projects a bundle-wide validated map onto the component selected for one turn. */
16
+ export function filterTierBindingForAgent(agent, tierBinding) {
17
+ return Object.fromEntries([...new Set(agent.steps.map((step) => step.tier))]
18
+ .filter((tier) => tierBinding[tier] !== undefined)
19
+ .map((tier) => [tier, tierBinding[tier]]));
20
+ }
21
+ /**
22
+ * Resolves the `rootDir` the native `write_artifact` tool's
23
+ * `createLocalExecutionEnv` scope is rooted at. Precedence: an explicit
24
+ * `outDir` (per-call override, e.g. `InProcessOptions.outDir` /
25
+ * `WREN_HARNESS_OUT`) > the `WREN_HARNESS_ARTIFACTS_DIR` env var
26
+ * (operator-wide override) > a fixed `os.tmpdir()` subdirectory.
27
+ * Deliberately NEVER `process.cwd()` — that was the bug this fixes:
28
+ * `runInProcessDefault` used to call `createLocalExecutionEnv()` with no
29
+ * `rootDir` at all, which defaults to `process.cwd()` (`harness/exec/local.ts`)
30
+ * — for a live BFF process that's wherever this package's server process
31
+ * happens to be launched from, so every `write_artifact` call (from
32
+ * `explain_change`/`generate_dashboard`, both scoped-write-gated) dropped its
33
+ * output straight into whatever directory started the process.
34
+ *
35
+ * This is entirely independent of `projectDir` (`options.userProject`),
36
+ * which is the ONLY thing that governs the `query` tool's exec cwd (see
37
+ * `createWrenQueryTool` in `harness/tools/native.ts` — it takes `projectDir` as
38
+ * its `cwd` directly, never touching `env`'s own `rootDir`). Changing this
39
+ * default never changes where `query` runs.
40
+ */
41
+ export function resolveArtifactsDir(outDir) {
42
+ return outDir ?? process.env[ARTIFACTS_DIR_ENV_VAR] ?? path.join(os.tmpdir(), "wren-harness-artifacts");
43
+ }
44
+ /**
45
+ * In-process ("api-key" | "local" | "gateway"): compile the profile to a vercel
46
+ * bundle (unless `options.bundle` overrides it, for tests), resolve which
47
+ * compiled agent to run (`options.agentId`, defaulting to
48
+ * `ANSWER_QUERY_AGENT_ID` — intent routing picks this upstream in
49
+ * `server/turn.ts`, not here), assemble a `RunAgentContext` bound either to a
50
+ * single adapter covering every tier that agent uses (the default), or —
51
+ * when `options.tierBinding` is set ("hybrid" mode) — to that non-uniform
52
+ * per-tier map, validated via `buildHybridTierBinding`; wire its `query` tool
53
+ * to the real `wren` CLI against `options.userProject` (unless
54
+ * `options.mcpServers` overrides it, for tests), and run it in-process via
55
+ * `runAgent`.
56
+ */
57
+ export async function runInProcessDefault(options) {
58
+ const bundle = options.bundle ?? (await describeBundle(options));
59
+ // Intent routing (server/turn.ts) picks the agent id when present; falls back to answer_query,
60
+ // the original default from before intent routing existed.
61
+ const agentId = options.agentId ?? ANSWER_QUERY_AGENT_ID;
62
+ const agent = bundle.agents.find((candidate) => candidate.id === agentId);
63
+ if (!agent) {
64
+ throw new Error(`compiled bundle has no "${agentId}" agent`);
65
+ }
66
+ // Runtime settings are validated against every tier declared by the
67
+ // compiled bundle, but one turn executes only one selected component. Keep
68
+ // that complete map at the server boundary and pass this agent's projection
69
+ // here; `buildHybridTierBinding` intentionally rejects tiers used solely by
70
+ // another component.
71
+ const agentTierBinding = options.tierBinding === undefined ? undefined : filterTierBindingForAgent(agent, options.tierBinding);
72
+ const binding = options.tierBinding !== undefined
73
+ ? buildHybridTierBinding(agent, agentTierBinding)
74
+ : buildUniformTierBinding(agent, deriveAdapterSpec(options.authChoice, options.model !== undefined ? { model: options.model } : {}));
75
+ const registry = createDefaultProviderRegistry();
76
+ const capabilityRegistry = createDefaultCapabilityRegistry();
77
+ if (options.mcpServers !== undefined) {
78
+ return runAgent(bundle, agentId, options.question, {
79
+ binding,
80
+ registry,
81
+ capabilityRegistry,
82
+ mcpServers: options.mcpServers,
83
+ ...(options.onEvent !== undefined ? { onEvent: options.onEvent } : {}),
84
+ });
85
+ }
86
+ // Preflight `wren` before wiring the native tool registry around
87
+ // it. Without this, a missing `wren` binary stayed invisible until the
88
+ // tool loop actually called `query` — surfacing as an opaque,
89
+ // indistinguishable-from-a-normal-failure exec result rather than a clear
90
+ // "wren is not installed" error at the point where it's actually
91
+ // diagnosable.
92
+ await resolveWrenBinary();
93
+ const policy = deriveEnforcement(agent);
94
+ // Scoped to an artifacts dir, never the default `process.cwd()`
95
+ // (see `resolveArtifactsDir`'s doc comment) — independent of `projectDir`
96
+ // below, which alone governs the `query` tool's exec cwd.
97
+ const env = createLocalExecutionEnv({ rootDir: resolveArtifactsDir(options.outDir) });
98
+ const nativeTools = createWrenNativeToolRegistry({ env, policy, projectDir: options.userProject });
99
+ return runAgent(bundle, agentId, options.question, {
100
+ binding,
101
+ registry,
102
+ capabilityRegistry,
103
+ nativeTools,
104
+ ...(options.onEvent !== undefined ? { onEvent: options.onEvent } : {}),
105
+ });
106
+ }
@@ -0,0 +1,19 @@
1
+ export { DEFAULT_LOCAL_ENDPOINT, DEFAULT_LOCAL_MODEL, deriveAdapterSpec } from "./adapter-spec.js";
2
+ export type { DeriveAdapterSpecOptions } from "./adapter-spec.js";
3
+ export { AgentSdkCliNotFoundError, resolveAgentSdkCli } from "./agent-sdk-cli.js";
4
+ export type { ResolvedCli } from "./agent-sdk-cli.js";
5
+ export { buildAgentSdkManifestArgs, runAgentSdkManifest } from "./agent-sdk-manifest.js";
6
+ export type { AgentSdkManifestCommand, BuildAgentSdkManifestArgsOptions } from "./agent-sdk-manifest.js";
7
+ export { describeBundle } from "./describe.js";
8
+ export type { DescribeBundleOptions } from "./describe.js";
9
+ export { resolveArtifactContent } from "./artifact-content.js";
10
+ export type { ArtifactContentDto, ArtifactContentUnavailableReason } from "./artifact-content.js";
11
+ export { buildAgentSdkChatArgs, DispatchedSessionError, runDispatchedDefault } from "./dispatched.js";
12
+ export type { AgentSdkChatCommand, BuildAgentSdkChatArgsOptions } from "./dispatched.js";
13
+ export { buildCodexAskArgs, runCodexAskDefault } from "./codex-ask.js";
14
+ export { CodexAskEventMapper } from "./codex-ask-events.js";
15
+ export { resolveArtifactsDir, runInProcessDefault } from "./in-process.js";
16
+ export { resolveDefaultEnrichIrPath, resolveDefaultProfileSource, resolveDefaultSetupIrPath } from "./profile-source.js";
17
+ export { route } from "./route.js";
18
+ export { buildHybridTierBinding, buildUniformTierBinding } from "./tier-binding.js";
19
+ export type { InProcessExecutor, InProcessOptions, CodexAskExecutor, CodexAskOptions, CodexAskResult, DispatchedExecutor, DispatchedOptions, DispatchedResult, RouteOptions, RouteResult, } from "./types.js";
@@ -0,0 +1,12 @@
1
+ export { DEFAULT_LOCAL_ENDPOINT, DEFAULT_LOCAL_MODEL, deriveAdapterSpec } from "./adapter-spec.js";
2
+ export { AgentSdkCliNotFoundError, resolveAgentSdkCli } from "./agent-sdk-cli.js";
3
+ export { buildAgentSdkManifestArgs, runAgentSdkManifest } from "./agent-sdk-manifest.js";
4
+ export { describeBundle } from "./describe.js";
5
+ export { resolveArtifactContent } from "./artifact-content.js";
6
+ export { buildAgentSdkChatArgs, DispatchedSessionError, runDispatchedDefault } from "./dispatched.js";
7
+ export { buildCodexAskArgs, runCodexAskDefault } from "./codex-ask.js";
8
+ export { CodexAskEventMapper } from "./codex-ask-events.js";
9
+ export { resolveArtifactsDir, runInProcessDefault } from "./in-process.js";
10
+ export { resolveDefaultEnrichIrPath, resolveDefaultProfileSource, resolveDefaultSetupIrPath } from "./profile-source.js";
11
+ export { route } from "./route.js";
12
+ export { buildHybridTierBinding, buildUniformTierBinding } from "./tier-binding.js";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Resolves the v1 default profile (`profiles/genbi-default`). Callers that
3
+ * already have an explicit `--profile`/`profileSource` never call this.
4
+ * Throws (loud-fail) if this package's own profile tree is not present.
5
+ */
6
+ export declare function resolveDefaultProfileSource(): string;
7
+ /**
8
+ * Resolves the committed genbi-setup IR (`profiles/genbi-setup/ir.golden.json`)
9
+ * via the same walk as {@link resolveDefaultProfileSource}. Unlike that
10
+ * function, this one does NOT hard-fail when it is absent — only the setup
11
+ * wizard's agentic connect step needs this IR, so a bootstrap-mode BFF instance
12
+ * that never uses `/api/setup/connect` should still boot fine without it.
13
+ * Callers should surface a clear error at the point of actually dispatching a
14
+ * setup turn if this returns `undefined`, not at boot.
15
+ */
16
+ export declare function resolveDefaultSetupIrPath(): string | undefined;
17
+ /**
18
+ * Resolves the committed genbi-enrich-context IR
19
+ * (`profiles/genbi-enrich-context/ir.golden.json`) via the same walk as
20
+ * {@link resolveDefaultSetupIrPath}. This profile's `context_precondition`
21
+ * ("an existing pinned Wren project ... that parses and has a successful
22
+ * build proof") is a host runtime obligation, not something the compiled IR
23
+ * itself is validated against.
24
+ *
25
+ * This used to say the golden was "dispatched against whichever project is
26
+ * actually bound at runtime via --project". That was the bug: a golden is the
27
+ * profile compiled once against its own fixture binding, so dispatching it
28
+ * described the example project's schema to an agent working on someone
29
+ * else's. An enrichment draft now compiles against the bound project (see the
30
+ * runner's `profileSource`), and this path remains only as the prebuilt
31
+ * fallback for a caller that has no profile source. Does NOT hard-fail when it
32
+ * is absent — only an enrichment draft call needs this; a BFF instance that
33
+ * never starts an enrichment run should still boot fine without it. Callers
34
+ * should surface a clear error at the point of actually dispatching a draft
35
+ * turn if this returns `undefined`, not at boot.
36
+ */
37
+ export declare function resolveDefaultEnrichIrPath(): string | undefined;
@@ -0,0 +1,76 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * The GenBI profiles (`genbi-default`, `genbi-setup`, `genbi-enrich-context`,
6
+ * `genbi-monitor`) live in this package's own `profiles/` tree, alongside their
7
+ * committed `ir.golden.json`. The `warble` binary and its dispatchers still
8
+ * come from a sibling Warble checkout (see `harness/compile/resolve-binary.ts`);
9
+ * only the behavior source is ours.
10
+ *
11
+ * Resolution walks up from this module's own location looking for an ancestor
12
+ * that contains `profiles/<id>`, so it works both from the TypeScript sources
13
+ * (`harness/route/`) and from the built bundle (`dist-server/harness/route/`),
14
+ * whether the caller runs from the main checkout or a nested git worktree.
15
+ */
16
+ function resolveProfileEntry(...segments) {
17
+ let dir = path.dirname(fileURLToPath(import.meta.url));
18
+ const maxDepth = 10;
19
+ for (let i = 0; i < maxDepth; i += 1) {
20
+ const candidate = path.join(dir, "profiles", ...segments);
21
+ if (existsSync(candidate))
22
+ return candidate;
23
+ const parent = path.dirname(dir);
24
+ if (parent === dir)
25
+ break; // reached filesystem root
26
+ dir = parent;
27
+ }
28
+ return undefined;
29
+ }
30
+ /**
31
+ * Resolves the v1 default profile (`profiles/genbi-default`). Callers that
32
+ * already have an explicit `--profile`/`profileSource` never call this.
33
+ * Throws (loud-fail) if this package's own profile tree is not present.
34
+ */
35
+ export function resolveDefaultProfileSource() {
36
+ const resolved = resolveProfileEntry("genbi-default");
37
+ if (resolved !== undefined)
38
+ return resolved;
39
+ throw new Error('no default profile source found (searched ancestors of this module for "<ancestor>/profiles/' +
40
+ 'genbi-default"); pass --profile explicitly');
41
+ }
42
+ /**
43
+ * Resolves the committed genbi-setup IR (`profiles/genbi-setup/ir.golden.json`)
44
+ * via the same walk as {@link resolveDefaultProfileSource}. Unlike that
45
+ * function, this one does NOT hard-fail when it is absent — only the setup
46
+ * wizard's agentic connect step needs this IR, so a bootstrap-mode BFF instance
47
+ * that never uses `/api/setup/connect` should still boot fine without it.
48
+ * Callers should surface a clear error at the point of actually dispatching a
49
+ * setup turn if this returns `undefined`, not at boot.
50
+ */
51
+ export function resolveDefaultSetupIrPath() {
52
+ return resolveProfileEntry("genbi-setup", "ir.golden.json");
53
+ }
54
+ /**
55
+ * Resolves the committed genbi-enrich-context IR
56
+ * (`profiles/genbi-enrich-context/ir.golden.json`) via the same walk as
57
+ * {@link resolveDefaultSetupIrPath}. This profile's `context_precondition`
58
+ * ("an existing pinned Wren project ... that parses and has a successful
59
+ * build proof") is a host runtime obligation, not something the compiled IR
60
+ * itself is validated against.
61
+ *
62
+ * This used to say the golden was "dispatched against whichever project is
63
+ * actually bound at runtime via --project". That was the bug: a golden is the
64
+ * profile compiled once against its own fixture binding, so dispatching it
65
+ * described the example project's schema to an agent working on someone
66
+ * else's. An enrichment draft now compiles against the bound project (see the
67
+ * runner's `profileSource`), and this path remains only as the prebuilt
68
+ * fallback for a caller that has no profile source. Does NOT hard-fail when it
69
+ * is absent — only an enrichment draft call needs this; a BFF instance that
70
+ * never starts an enrichment run should still boot fine without it. Callers
71
+ * should surface a clear error at the point of actually dispatching a draft
72
+ * turn if this returns `undefined`, not at boot.
73
+ */
74
+ export function resolveDefaultEnrichIrPath() {
75
+ return resolveProfileEntry("genbi-enrich-context", "ir.golden.json");
76
+ }
@@ -0,0 +1,33 @@
1
+ import type { RouteOptions, RouteResult } from "./types.js";
2
+ /**
3
+ * The single seam mapping a resolved `AuthChoice` to a back-end:
4
+ * Claude subscription -> dispatched (warble-agent-sdk), Codex subscription ->
5
+ * codex:local, everything else
6
+ * (`api-key`/`local`/`gateway`) -> in-process (compile to a vercel bundle and run
7
+ * In-process via `runAgent`). `inProcess`/`dispatched` are injectable so callers and
8
+ * tests can stub either back-end without touching the other or invoking any
9
+ * real compile/dispatch — this is the hook point later tickets (compliance
10
+ * gating, hybrid dispatch) attach to without needing to touch the CLI or
11
+ * either mode's internals.
12
+ *
13
+ * The compliance gate (`enforceCompliance`) runs first, before either
14
+ * back-end is invoked: a `subscription` `AuthChoice` used for a `"hosted"`
15
+ * deployment throws `ComplianceError` here, so no caller of `route()` can
16
+ * reach dispatched for a use the ToS doesn't allow, regardless of whether it
17
+ * came through the CLI's own (separate, warning-printing) check. The
18
+ * resulting `warnings` (e.g. `SUBSCRIPTION_TOS_WARNING`) are attached to the
19
+ * returned `RouteResult` so programmatic callers of `route()` see them too
20
+ * — the CLI additionally prints them to stderr from its own, separately
21
+ * timed `enforceCompliance` call (before `route()` is even invoked, so they
22
+ * print before the back-end runs rather than after `route()` resolves), but
23
+ * that's an independent concern from surfacing them on the result value
24
+ * here. `runDispatchedDefault` also re-runs this same gate as a belt for direct
25
+ * (non-`route()`) callers — see its doc comment.
26
+ *
27
+ * The per-tier "hybrid" override: `options.tierBinding` (in-process's per-tier override) and
28
+ * `options.modelsConfig` (dispatched's `--models-config` passthrough) are each
29
+ * meaningful for exactly one back-end. Rather than silently ignore one when
30
+ * the `authChoice` picks the other back-end, `route()` loud-fails — the
31
+ * same "no silent misrouting" posture as the existing dispatched provider guard.
32
+ */
33
+ export declare function route(options: RouteOptions): Promise<RouteResult>;
@@ -0,0 +1,114 @@
1
+ import { enforceCompliance } from "../compliance/index.js";
2
+ import { runInProcessDefault } from "./in-process.js";
3
+ import { runDispatchedDefault } from "./dispatched.js";
4
+ import { runCodexAskDefault } from "./codex-ask.js";
5
+ /**
6
+ * The single seam mapping a resolved `AuthChoice` to a back-end:
7
+ * Claude subscription -> dispatched (warble-agent-sdk), Codex subscription ->
8
+ * codex:local, everything else
9
+ * (`api-key`/`local`/`gateway`) -> in-process (compile to a vercel bundle and run
10
+ * In-process via `runAgent`). `inProcess`/`dispatched` are injectable so callers and
11
+ * tests can stub either back-end without touching the other or invoking any
12
+ * real compile/dispatch — this is the hook point later tickets (compliance
13
+ * gating, hybrid dispatch) attach to without needing to touch the CLI or
14
+ * either mode's internals.
15
+ *
16
+ * The compliance gate (`enforceCompliance`) runs first, before either
17
+ * back-end is invoked: a `subscription` `AuthChoice` used for a `"hosted"`
18
+ * deployment throws `ComplianceError` here, so no caller of `route()` can
19
+ * reach dispatched for a use the ToS doesn't allow, regardless of whether it
20
+ * came through the CLI's own (separate, warning-printing) check. The
21
+ * resulting `warnings` (e.g. `SUBSCRIPTION_TOS_WARNING`) are attached to the
22
+ * returned `RouteResult` so programmatic callers of `route()` see them too
23
+ * — the CLI additionally prints them to stderr from its own, separately
24
+ * timed `enforceCompliance` call (before `route()` is even invoked, so they
25
+ * print before the back-end runs rather than after `route()` resolves), but
26
+ * that's an independent concern from surfacing them on the result value
27
+ * here. `runDispatchedDefault` also re-runs this same gate as a belt for direct
28
+ * (non-`route()`) callers — see its doc comment.
29
+ *
30
+ * The per-tier "hybrid" override: `options.tierBinding` (in-process's per-tier override) and
31
+ * `options.modelsConfig` (dispatched's `--models-config` passthrough) are each
32
+ * meaningful for exactly one back-end. Rather than silently ignore one when
33
+ * the `authChoice` picks the other back-end, `route()` loud-fails — the
34
+ * same "no silent misrouting" posture as the existing dispatched provider guard.
35
+ */
36
+ export async function route(options) {
37
+ const runInProcess = options.inProcess ?? runInProcessDefault;
38
+ const runDispatched = options.dispatched ?? runDispatchedDefault;
39
+ const runCodexAsk = options.codexAsk ?? runCodexAskDefault;
40
+ const { authChoice } = options;
41
+ const deployment = options.deployment ?? "personal";
42
+ const { warnings } = enforceCompliance(authChoice, { deployment });
43
+ if (authChoice.mode === "subscription") {
44
+ if (options.tierBinding !== undefined) {
45
+ throw new Error("tierBinding (hybrid in-process per-tier routing) has no effect under a subscription " +
46
+ "authChoice — dispatched has no adapter/tier binding of its own; use modelsConfig instead " +
47
+ "(warble-agent-sdk's own --models-config per-step routing)");
48
+ }
49
+ if (authChoice.provider === "codex") {
50
+ if (options.modelsConfig !== undefined) {
51
+ throw new Error("modelsConfig applies only to the Claude subscription dispatcher, not codex:local Ask");
52
+ }
53
+ const result = await runCodexAsk({
54
+ authChoice: { ...authChoice, provider: "codex" },
55
+ profileSource: options.profileSource,
56
+ userProject: options.userProject,
57
+ question: options.question,
58
+ deployment,
59
+ ...(options.warbleBin !== undefined ? { warbleBin: options.warbleBin } : {}),
60
+ ...(options.workDir !== undefined ? { workDir: options.workDir } : {}),
61
+ ...(options.onEvent !== undefined ? { onEvent: options.onEvent } : {}),
62
+ ...(options.agentId !== undefined ? { agentId: options.agentId } : {}),
63
+ ...(options.codexModels !== undefined ? { codexModels: options.codexModels } : {}),
64
+ ...(options.codexHome !== undefined ? { codexHome: options.codexHome } : {}),
65
+ ...(options.codexLocalBin !== undefined ? { codexLocalBin: options.codexLocalBin } : {}),
66
+ ...(options.codexLocalCli !== undefined ? { codexLocalCli: options.codexLocalCli } : {}),
67
+ ...(options.codexBin !== undefined ? { codexBin: options.codexBin } : {}),
68
+ ...(options.codexMcpServer !== undefined ? { mcpServer: options.codexMcpServer } : {}),
69
+ ...(options.chatTimeoutMs !== undefined ? { timeoutMs: options.chatTimeoutMs } : {}),
70
+ ...(options.signal !== undefined ? { signal: options.signal } : {}),
71
+ });
72
+ return { backend: "codex-local", warnings, ...result };
73
+ }
74
+ const result = await runDispatched({
75
+ authChoice,
76
+ profileSource: options.profileSource,
77
+ userProject: options.userProject,
78
+ question: options.question,
79
+ deployment,
80
+ ...(options.warbleBin !== undefined ? { warbleBin: options.warbleBin } : {}),
81
+ ...(options.agentSdkBin !== undefined ? { agentSdkBin: options.agentSdkBin } : {}),
82
+ ...(options.outDir !== undefined ? { outDir: options.outDir } : {}),
83
+ ...(options.workDir !== undefined ? { workDir: options.workDir } : {}),
84
+ ...(options.modelsConfig !== undefined ? { modelsConfig: options.modelsConfig } : {}),
85
+ ...(options.onEvent !== undefined ? { onEvent: options.onEvent } : {}),
86
+ ...(options.agentId !== undefined ? { agentId: options.agentId } : {}),
87
+ ...(options.chatTimeoutMs !== undefined ? { chatTimeoutMs: options.chatTimeoutMs } : {}),
88
+ ...(options.signal !== undefined ? { signal: options.signal } : {}),
89
+ });
90
+ return { backend: "agent-sdk", warnings, ...result };
91
+ }
92
+ if (options.modelsConfig !== undefined) {
93
+ throw new Error(`modelsConfig (hybrid dispatched --models-config passthrough) has no effect under a ` +
94
+ `"${authChoice.mode}" authChoice — that flag only reaches warble-agent-sdk's chat ` +
95
+ "invocation, which in-process never shells; use tierBinding instead (this harness's own " +
96
+ "per-tier adapter map)");
97
+ }
98
+ const result = await runInProcess({
99
+ authChoice,
100
+ profileSource: options.profileSource,
101
+ userProject: options.userProject,
102
+ question: options.question,
103
+ ...(options.model !== undefined ? { model: options.model } : {}),
104
+ ...(options.warbleBin !== undefined ? { warbleBin: options.warbleBin } : {}),
105
+ ...(options.workDir !== undefined ? { workDir: options.workDir } : {}),
106
+ ...(options.outDir !== undefined ? { outDir: options.outDir } : {}),
107
+ ...(options.bundle !== undefined ? { bundle: options.bundle } : {}),
108
+ ...(options.mcpServers !== undefined ? { mcpServers: options.mcpServers } : {}),
109
+ ...(options.tierBinding !== undefined ? { tierBinding: options.tierBinding } : {}),
110
+ ...(options.onEvent !== undefined ? { onEvent: options.onEvent } : {}),
111
+ ...(options.agentId !== undefined ? { agentId: options.agentId } : {}),
112
+ });
113
+ return { backend: "agent", warnings, ...result };
114
+ }
@@ -0,0 +1,36 @@
1
+ import type { Agent } from "../bundle/schema.js";
2
+ import type { AdapterSpec, TierBinding } from "../providers/index.js";
3
+ /**
4
+ * Builds a `TierBinding` mapping every distinct `tier` name used by `agent`'s
5
+ * steps to the same `adapterSpec` — one model realizing every tier. Tier
6
+ * names are open strings owned by individual steps, not
7
+ * declared centrally on the agent, so a single-adapter run must enumerate
8
+ * them from the bundle itself rather than assuming fixed names like
9
+ * `cheap`/`strong`. This also covers the render envelope stage automatically:
10
+ * `renderEnvelope`'s default tier is always the tier of the agent's last
11
+ * `independent` step, which is necessarily one of the tiers already
12
+ * collected here.
13
+ */
14
+ export declare function buildUniformTierBinding(agent: Agent, adapterSpec: AdapterSpec): TierBinding;
15
+ /**
16
+ * Builds a "hybrid" `TierBinding` — a NON-uniform map from `agent`'s
17
+ * tiers to independently-chosen `AdapterSpec`s (e.g. `cheap` on a free local
18
+ * model, `strong` on a paid cloud one). Unlike `buildUniformTierBinding`
19
+ * (which derives its single map from one `AdapterSpec`, so it can never be
20
+ * short a tier), this takes a caller-supplied per-tier map and must validate
21
+ * it against the agent's actual tiers before trusting it:
22
+ *
23
+ * - every tier `agent`'s steps use must have an entry in `tiers` — a step
24
+ * whose tier resolves to nothing would otherwise surface late, deep inside
25
+ * `resolveTierModel`, as a bare `UnknownTierError` with no hint that the
26
+ * hybrid map (not the agent) is what's incomplete.
27
+ * - every entry in `tiers` must name a tier `agent` actually uses — an entry
28
+ * for a typo'd or stale tier name would otherwise silently do nothing
29
+ * (the intended tier stays unbound) rather than fail at the point the typo
30
+ * was made.
31
+ *
32
+ * Both failure modes loud-fail here, before `runAgent` starts, naming the
33
+ * agent and every offending tier — mirroring this module's/`adapter-spec.ts`'s
34
+ * existing loud-fail style (e.g. `gateway` mode's missing `baseURL`/`model`).
35
+ */
36
+ export declare function buildHybridTierBinding(agent: Agent, tiers: Readonly<Record<string, AdapterSpec>>): TierBinding;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Builds a `TierBinding` mapping every distinct `tier` name used by `agent`'s
3
+ * steps to the same `adapterSpec` — one model realizing every tier. Tier
4
+ * names are open strings owned by individual steps, not
5
+ * declared centrally on the agent, so a single-adapter run must enumerate
6
+ * them from the bundle itself rather than assuming fixed names like
7
+ * `cheap`/`strong`. This also covers the render envelope stage automatically:
8
+ * `renderEnvelope`'s default tier is always the tier of the agent's last
9
+ * `independent` step, which is necessarily one of the tiers already
10
+ * collected here.
11
+ */
12
+ export function buildUniformTierBinding(agent, adapterSpec) {
13
+ const tiers = {};
14
+ for (const step of agent.steps) {
15
+ tiers[step.tier] = adapterSpec;
16
+ }
17
+ return { tiers };
18
+ }
19
+ /** The distinct `tier` names `agent`'s steps use, in first-seen order. */
20
+ function agentTierNames(agent) {
21
+ const seen = new Set();
22
+ for (const step of agent.steps) {
23
+ seen.add(step.tier);
24
+ }
25
+ return [...seen];
26
+ }
27
+ /**
28
+ * Builds a "hybrid" `TierBinding` — a NON-uniform map from `agent`'s
29
+ * tiers to independently-chosen `AdapterSpec`s (e.g. `cheap` on a free local
30
+ * model, `strong` on a paid cloud one). Unlike `buildUniformTierBinding`
31
+ * (which derives its single map from one `AdapterSpec`, so it can never be
32
+ * short a tier), this takes a caller-supplied per-tier map and must validate
33
+ * it against the agent's actual tiers before trusting it:
34
+ *
35
+ * - every tier `agent`'s steps use must have an entry in `tiers` — a step
36
+ * whose tier resolves to nothing would otherwise surface late, deep inside
37
+ * `resolveTierModel`, as a bare `UnknownTierError` with no hint that the
38
+ * hybrid map (not the agent) is what's incomplete.
39
+ * - every entry in `tiers` must name a tier `agent` actually uses — an entry
40
+ * for a typo'd or stale tier name would otherwise silently do nothing
41
+ * (the intended tier stays unbound) rather than fail at the point the typo
42
+ * was made.
43
+ *
44
+ * Both failure modes loud-fail here, before `runAgent` starts, naming the
45
+ * agent and every offending tier — mirroring this module's/`adapter-spec.ts`'s
46
+ * existing loud-fail style (e.g. `gateway` mode's missing `baseURL`/`model`).
47
+ */
48
+ export function buildHybridTierBinding(agent, tiers) {
49
+ const agentTiers = agentTierNames(agent);
50
+ const agentTierSet = new Set(agentTiers);
51
+ const boundTierSet = new Set(Object.keys(tiers));
52
+ const missing = agentTiers.filter((tier) => !boundTierSet.has(tier));
53
+ if (missing.length > 0) {
54
+ throw new Error(`hybrid tier binding is missing an adapter for tier(s): ${missing.join(", ")} — ` +
55
+ `agent "${agent.id}" uses tier(s): ${agentTiers.join(", ")}`);
56
+ }
57
+ const unknown = [...boundTierSet].filter((tier) => !agentTierSet.has(tier));
58
+ if (unknown.length > 0) {
59
+ throw new Error(`hybrid tier binding names unknown tier(s) not used by agent "${agent.id}": ${unknown.join(", ")} — ` +
60
+ `known tier(s): ${agentTiers.join(", ")}`);
61
+ }
62
+ return { tiers };
63
+ }