@try-works/dsh-model-as-tool 0.1.2 → 0.3.6

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 (267) hide show
  1. package/bin/dsh-tool-mode-credentials.mjs +20 -0
  2. package/bin/dsh-tool-mode.mjs +202 -0
  3. package/catalog/desert-ant-models.json +693 -693
  4. package/client/client.model-tools.js +592 -0
  5. package/client/index.js +1679 -0
  6. package/lib/adapters/index.d.ts.map +1 -1
  7. package/lib/adapters/index.js +4 -4
  8. package/lib/bindings.d.ts.map +1 -1
  9. package/lib/bindings.js +26 -25
  10. package/lib/capability.d.ts.map +1 -1
  11. package/lib/capability.js +17 -0
  12. package/lib/capture-path.d.ts +97 -0
  13. package/lib/capture-path.d.ts.map +1 -0
  14. package/lib/capture-path.js +152 -0
  15. package/lib/diagnostics.d.ts +166 -0
  16. package/lib/diagnostics.d.ts.map +1 -0
  17. package/lib/diagnostics.js +135 -0
  18. package/lib/extension-dependencies.d.ts +197 -0
  19. package/lib/extension-dependencies.d.ts.map +1 -0
  20. package/lib/extension-dependencies.js +369 -0
  21. package/lib/extension-dispatch.d.ts +117 -0
  22. package/lib/extension-dispatch.d.ts.map +1 -0
  23. package/lib/extension-dispatch.js +124 -0
  24. package/lib/extension-registrar.d.ts +100 -0
  25. package/lib/extension-registrar.d.ts.map +1 -0
  26. package/lib/extension-registrar.js +134 -0
  27. package/lib/extensions-api.d.ts +217 -0
  28. package/lib/extensions-api.d.ts.map +1 -0
  29. package/lib/extensions-api.js +17 -0
  30. package/lib/extensions-remote.d.ts +54 -0
  31. package/lib/extensions-remote.d.ts.map +1 -0
  32. package/lib/extensions-remote.js +66 -0
  33. package/lib/extensions-service.d.ts +334 -0
  34. package/lib/extensions-service.d.ts.map +1 -0
  35. package/lib/extensions-service.js +1351 -0
  36. package/lib/harness-llm.d.ts +138 -0
  37. package/lib/harness-llm.d.ts.map +1 -0
  38. package/lib/harness-llm.js +218 -0
  39. package/lib/harness-serving.d.ts +95 -0
  40. package/lib/harness-serving.d.ts.map +1 -0
  41. package/lib/harness-serving.js +157 -0
  42. package/lib/headless.d.ts +97 -0
  43. package/lib/headless.d.ts.map +1 -0
  44. package/lib/headless.js +349 -0
  45. package/lib/http-acquisition.d.ts +27 -0
  46. package/lib/http-acquisition.d.ts.map +1 -0
  47. package/lib/http-acquisition.js +136 -0
  48. package/lib/index.d.ts +250 -7
  49. package/lib/index.d.ts.map +1 -1
  50. package/lib/index.js +2625 -70
  51. package/lib/invocation-accounting.d.ts +169 -0
  52. package/lib/invocation-accounting.d.ts.map +1 -0
  53. package/lib/invocation-accounting.js +148 -0
  54. package/lib/legacy-migration.d.ts +95 -0
  55. package/lib/legacy-migration.d.ts.map +1 -0
  56. package/lib/legacy-migration.js +429 -0
  57. package/lib/managed-workers.d.ts +151 -0
  58. package/lib/managed-workers.d.ts.map +1 -0
  59. package/lib/managed-workers.js +249 -0
  60. package/lib/mcp/client-grant.d.ts +74 -0
  61. package/lib/mcp/client-grant.d.ts.map +1 -0
  62. package/lib/mcp/client-grant.js +60 -0
  63. package/lib/mcp/credential-cli.d.ts +38 -0
  64. package/lib/mcp/credential-cli.d.ts.map +1 -0
  65. package/lib/mcp/credential-cli.js +148 -0
  66. package/lib/mcp/credential-store.d.ts +143 -0
  67. package/lib/mcp/credential-store.d.ts.map +1 -0
  68. package/lib/mcp/credential-store.js +454 -0
  69. package/lib/mcp/host-binding.d.ts +25 -0
  70. package/lib/mcp/host-binding.d.ts.map +1 -1
  71. package/lib/mcp/host-binding.js +107 -14
  72. package/lib/mcp/http.d.ts +68 -18
  73. package/lib/mcp/http.d.ts.map +1 -1
  74. package/lib/mcp/http.js +390 -62
  75. package/lib/mcp/oauth.d.ts +149 -0
  76. package/lib/mcp/oauth.d.ts.map +1 -0
  77. package/lib/mcp/oauth.js +396 -0
  78. package/lib/mcp/protocol.d.ts +46 -7
  79. package/lib/mcp/protocol.d.ts.map +1 -1
  80. package/lib/mcp/protocol.js +63 -16
  81. package/lib/mcp/rate-limit.d.ts +68 -0
  82. package/lib/mcp/rate-limit.d.ts.map +1 -0
  83. package/lib/mcp/rate-limit.js +114 -0
  84. package/lib/mcp/sdk-v2.d.ts +17 -0
  85. package/lib/mcp/sdk-v2.d.ts.map +1 -0
  86. package/lib/mcp/sdk-v2.js +181 -0
  87. package/lib/mcp/server.d.ts +11 -1
  88. package/lib/mcp/server.d.ts.map +1 -1
  89. package/lib/mcp/server.js +80 -13
  90. package/lib/mcp/stdio.d.ts.map +1 -1
  91. package/lib/mcp/stdio.js +9 -1
  92. package/lib/mcp/subscriptions.d.ts +43 -1
  93. package/lib/mcp/subscriptions.d.ts.map +1 -1
  94. package/lib/mcp/subscriptions.js +90 -9
  95. package/lib/model-qualification.d.ts +192 -0
  96. package/lib/model-qualification.d.ts.map +1 -0
  97. package/lib/model-qualification.js +178 -0
  98. package/lib/planning.d.ts +246 -0
  99. package/lib/planning.d.ts.map +1 -0
  100. package/lib/planning.js +457 -0
  101. package/lib/prepared-assets.d.ts +178 -0
  102. package/lib/prepared-assets.d.ts.map +1 -0
  103. package/lib/prepared-assets.js +286 -0
  104. package/lib/provider-bridge.d.ts +37 -0
  105. package/lib/provider-bridge.d.ts.map +1 -1
  106. package/lib/provider-bridge.js +189 -12
  107. package/lib/ptc-authority.d.ts +140 -0
  108. package/lib/ptc-authority.d.ts.map +1 -0
  109. package/lib/ptc-authority.js +216 -0
  110. package/lib/ptc-runtime.d.ts +156 -0
  111. package/lib/ptc-runtime.d.ts.map +1 -0
  112. package/lib/ptc-runtime.js +191 -0
  113. package/lib/publication.d.ts +2 -1
  114. package/lib/publication.d.ts.map +1 -1
  115. package/lib/publication.js +13 -5
  116. package/lib/receipt.d.ts.map +1 -1
  117. package/lib/receipt.js +8 -0
  118. package/lib/title-proposal.d.ts +194 -0
  119. package/lib/title-proposal.d.ts.map +1 -0
  120. package/lib/title-proposal.js +244 -0
  121. package/lib/tool-mode-run.d.ts +235 -0
  122. package/lib/tool-mode-run.d.ts.map +1 -0
  123. package/lib/tool-mode-run.js +283 -0
  124. package/lib/types.d.ts +3 -1
  125. package/lib/types.d.ts.map +1 -1
  126. package/lib/update-surface.d.ts +79 -0
  127. package/lib/update-surface.d.ts.map +1 -0
  128. package/lib/update-surface.js +224 -0
  129. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/lang-detect.d.ts +16 -0
  130. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/lang-detect.d.ts.map +1 -0
  131. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/lang-detect.js +85 -0
  132. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts +38 -0
  133. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts.map +1 -0
  134. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.js +445 -0
  135. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/stt-transcribe.d.ts +20 -0
  136. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/stt-transcribe.d.ts.map +1 -0
  137. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/stt-transcribe.js +112 -0
  138. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts +14 -0
  139. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts.map +1 -0
  140. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.js +63 -0
  141. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts +14 -0
  142. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts.map +1 -0
  143. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.js +76 -0
  144. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts +82 -0
  145. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -0
  146. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +274 -0
  147. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +26 -0
  148. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -0
  149. package/node_modules/@try-works/tool-mode-contracts/lib/index.js +48 -0
  150. package/node_modules/@try-works/tool-mode-contracts/lib/meta.d.ts +104 -0
  151. package/node_modules/@try-works/tool-mode-contracts/lib/meta.d.ts.map +1 -0
  152. package/node_modules/@try-works/tool-mode-contracts/lib/meta.js +55 -0
  153. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +107 -0
  154. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -0
  155. package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +246 -0
  156. package/node_modules/@try-works/tool-mode-contracts/package.json +34 -0
  157. package/node_modules/@try-works/tool-mode-extension-sdk/bin/dsh-tool-mode.mjs +20 -0
  158. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts +80 -0
  159. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts.map +1 -0
  160. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.js +533 -0
  161. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts +83 -0
  162. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts.map +1 -0
  163. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.js +125 -0
  164. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts +70 -0
  165. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts.map +1 -0
  166. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.js +201 -0
  167. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts +57 -0
  168. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts.map +1 -0
  169. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.js +83 -0
  170. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts +31 -0
  171. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts.map +1 -0
  172. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.js +31 -0
  173. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts +86 -0
  174. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts.map +1 -0
  175. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.js +185 -0
  176. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts +126 -0
  177. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts.map +1 -0
  178. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.js +297 -0
  179. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts +216 -0
  180. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts.map +1 -0
  181. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.js +800 -0
  182. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts +210 -0
  183. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts.map +1 -0
  184. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.js +368 -0
  185. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts +63 -0
  186. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts.map +1 -0
  187. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.js +149 -0
  188. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts +35 -0
  189. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts.map +1 -0
  190. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.js +179 -0
  191. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts +179 -0
  192. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts.map +1 -0
  193. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.js +235 -0
  194. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts +367 -0
  195. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts.map +1 -0
  196. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.js +807 -0
  197. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
  198. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
  199. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
  200. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
  201. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
  202. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
  203. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
  204. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
  205. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
  206. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
  207. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
  208. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
  209. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
  210. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
  211. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
  212. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
  213. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
  214. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
  215. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
  216. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
  217. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
  218. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
  219. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
  220. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
  221. package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
  222. package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
  223. package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
  224. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
  225. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
  226. package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
  227. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
  228. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
  229. package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
  230. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
  231. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
  232. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
  233. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
  234. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
  235. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
  236. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
  237. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
  238. package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
  239. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
  240. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
  241. package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
  242. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
  243. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
  244. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
  245. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
  246. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
  247. package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
  248. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
  249. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
  250. package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
  251. package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
  252. package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
  253. package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
  254. package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
  255. package/package.json +50 -17
  256. package/remote/spec.mjs +439 -0
  257. package/remote/typert.host.mjs +14 -0
  258. package/remote/typert.remote-client.mjs +16 -0
  259. package/lib/adapters/tongue.d.ts +0 -31
  260. package/lib/adapters/tongue.d.ts.map +0 -1
  261. package/lib/adapters/tongue.js +0 -93
  262. package/lib/adapters/voz.d.ts +0 -28
  263. package/lib/adapters/voz.d.ts.map +0 -1
  264. package/lib/adapters/voz.js +0 -102
  265. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
  266. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
  267. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
@@ -0,0 +1,592 @@
1
+ /**
2
+ * The DSH LLM broker: one provider-neutral harness executor for declarative recipes
3
+ * (HLM-01, HLM-02, HLM-05, HLM-06, HLM-07, HLM-09, HLM-10, HLM-11).
4
+ *
5
+ * The broker's whole job is to build ONE request that contains nothing but the recipe's
6
+ * instructions and the validated bound inputs, and then to report honestly what came
7
+ * back. Every invariant here is a way the request could stop being that request, or the
8
+ * result stop being the result:
9
+ *
10
+ * - **The request is isolated by construction.** It is built from the recipe and the
11
+ * bound values alone. There is no parameter through which parent history, a parent
12
+ * system prompt, a tool catalog, skills, attachments or memory could arrive, and the
13
+ * shape is frozen so nothing can be added to it after validation (HLM-05, AT-68).
14
+ * - **Images are typed media, or the call is refused.** An image recipe on a provider
15
+ * whose capabilities do not cover images fails with `IMAGE_INPUT_UNAVAILABLE`. It does
16
+ * not quietly degrade to a text-only call that "describes" an image the model never
17
+ * received — that fallback produces a fluent invention, which is worse than an error
18
+ * (HLM-11).
19
+ * - **An unknown capability is not permission.** A provider that has not reported its
20
+ * image support fails with `EXECUTOR_CAPABILITY_UNKNOWN` rather than being assumed
21
+ * capable (MODEL-06's three-state rule, reused here).
22
+ * - **Remote is a policy decision, not an implementation detail.** `permitsDestination`
23
+ * is mirrored from RES-10 so a local-only profile refuses a remote provider however
24
+ * the tool was built.
25
+ * - **Cancellation is reported as cancellation.** An aborted signal can never produce a
26
+ * success, and never an empty one: an empty success is exactly what a caller writes
27
+ * into a document (HLM-10).
28
+ * - **Refusal, truncation, an empty completion and a stray tool call are errors, each
29
+ * with its own code.** HLM-07 requires them to be distinct failures, because a caller
30
+ * that cannot tell "the model refused" from "the model was cut off" cannot respond
31
+ * correctly to either (RCP-11).
32
+ * - **Recording is a precondition, not a side effect.** A recipe that declines recording
33
+ * under a host that requires it is refused with `CAPTURE_POLICY_CONFLICT` BEFORE the
34
+ * provider is called, because refusing after the call would report a refusal for work
35
+ * that has already been billed (HLM-09).
36
+ *
37
+ * @module @try-works/tool-mode-recipes/broker
38
+ */
39
+ import { errorCodeOf, INITIAL_MAX_IMAGE_INPUTS, INITIAL_MAX_OUTPUT_TOKENS, renderPrompt, validateRecipeInputs, } from "./recipe-format.js";
40
+ /**
41
+ * The initial policy: RES-10's initial per-leaf maxima, with recording required.
42
+ *
43
+ * Recording defaults to REQUIRED because HLM-09 makes reconstructability a DSH
44
+ * requirement, and a default of "off" is how a harness call ends up unrecorded without
45
+ * anyone deciding that.
46
+ */
47
+ export const INITIAL_HOST_POLICY = {
48
+ requireRecording: true,
49
+ remoteDestinations: true,
50
+ outputTokens: INITIAL_MAX_OUTPUT_TOKENS,
51
+ imageInputs: INITIAL_MAX_IMAGE_INPUTS,
52
+ };
53
+ /** A local-only policy, which forbids remote destinations however the tool was built. */
54
+ export const LOCAL_ONLY_HOST_POLICY = {
55
+ ...INITIAL_HOST_POLICY,
56
+ remoteDestinations: false,
57
+ };
58
+ /**
59
+ * Whether a policy permits reaching a destination (RES-10).
60
+ *
61
+ * Mirrored from the host's `harness-budgets.permitsDestination`: a local-only profile
62
+ * prohibits remote destinations INDEPENDENTLY of how the tool was implemented, so this
63
+ * asks the policy rather than the implementation. `local` is always permitted.
64
+ * @param policy - the policy in force.
65
+ * @param destination - the destination being considered.
66
+ * @returns whether it is permitted.
67
+ */
68
+ export function permitsDestination(policy, destination) {
69
+ return destination === 'local' ? true : policy.remoteDestinations;
70
+ }
71
+ /**
72
+ * Narrow a policy by a requested reduction (RES-10, HLM-07).
73
+ *
74
+ * Every figure takes the MINIMUM, so a request can only lower a limit; that direction is
75
+ * the point, since a caller asking for more than the profile permits must get the
76
+ * profile's figure. `requireRecording` is an OR: a request can demand recording where
77
+ * the base was silent, and can never switch it off.
78
+ * @param base - the policy in force.
79
+ * @param requested - what the caller asked for.
80
+ * @returns the effective policy.
81
+ */
82
+ export function narrowBrokerPolicy(base, requested) {
83
+ const lower = (a, b) => (b === undefined ? a : Math.min(a, b));
84
+ return {
85
+ requireRecording: base.requireRecording || (requested.requireRecording ?? false),
86
+ // A reduction can never ENABLE remote destinations, so this is an AND.
87
+ remoteDestinations: base.remoteDestinations && (requested.remoteDestinations ?? true),
88
+ outputTokens: lower(base.outputTokens, requested.outputTokens),
89
+ imageInputs: lower(base.imageInputs, requested.imageInputs),
90
+ };
91
+ }
92
+ /** Raised by {@link HarnessBroker.runOrThrow}. */
93
+ export class BrokerError extends Error {
94
+ name = 'BrokerError';
95
+ /** The failure code. */
96
+ code;
97
+ constructor(code, message) {
98
+ super(`${code}: ${message}`);
99
+ this.code = code;
100
+ }
101
+ }
102
+ /** Normalise the two-state shorthand into the three-state capability. */
103
+ function imageCapability(value) {
104
+ if (value === true)
105
+ return 'supported';
106
+ if (value === false)
107
+ return 'unsupported';
108
+ return value;
109
+ }
110
+ /** Name a thrown value for a failure reason. */
111
+ function messageOf(error) {
112
+ try {
113
+ return error instanceof Error ? error.message : String(error);
114
+ }
115
+ catch {
116
+ return '<unrenderable>';
117
+ }
118
+ }
119
+ /** The codes a binding error may carry, most specific first in the report. */
120
+ const BINDING_CODES = ['RECIPE_BINDING_ERROR', 'MODEL_BUDGET_EXCEEDED', 'UNSUPPORTED_MODALITY'];
121
+ /**
122
+ * The code a binding failure reports.
123
+ *
124
+ * The individual errors carry their own codes, and collapsing a specific one into a
125
+ * blanket `RECIPE_BINDING_ERROR` would destroy the distinction RCP-11 exists to keep: a
126
+ * caller that cannot tell "you sent a PDF where an image belongs" from "you misspelled a
127
+ * field" cannot respond correctly to either. The first error is used, because that is the
128
+ * one the caller has to fix first.
129
+ * @param errors - the rendered binding errors.
130
+ * @returns the code to report.
131
+ */
132
+ function bindingCode(errors) {
133
+ const code = errorCodeOf(errors[0] ?? '');
134
+ return BINDING_CODES.find(candidate => candidate === code) ?? 'RECIPE_BINDING_ERROR';
135
+ }
136
+ /**
137
+ * Build the ONE request a recipe can produce (HLM-05).
138
+ *
139
+ * Exported because the isolation property deserves to be checkable without a provider:
140
+ * the request object's key set IS the claim that nothing else is sent.
141
+ * @param recipe - the recipe being run.
142
+ * @param values - validated bindings.
143
+ * @param maxOutputTokens - the effective ceiling.
144
+ * @param signal - the cancellation channel.
145
+ * @returns the frozen request.
146
+ */
147
+ export function buildIsolatedRequest(recipe, values, maxOutputTokens, signal) {
148
+ const images = imagesOf(recipe, values);
149
+ return Object.freeze({
150
+ prompt: renderPrompt(recipe, values),
151
+ ...(images.length === 0
152
+ ? {}
153
+ : { images: Object.freeze(images.map(image => Object.freeze({ mediaType: image.mediaType, bytes: image.bytes }))) }),
154
+ maxOutputTokens,
155
+ signal,
156
+ /*
157
+ * `system` is absent, and that is the assertion: a recipe in this format cannot
158
+ * supply a system prompt, and the parent's must not be inherited (HLM-05). A request
159
+ * with a system member would be one built from something other than the recipe.
160
+ */
161
+ });
162
+ }
163
+ /** The bound images of a recipe, in declaration order. */
164
+ function imagesOf(recipe, values) {
165
+ const images = [];
166
+ for (const [name, declaration] of Object.entries(recipe.inputs)) {
167
+ if (declaration.type !== 'image')
168
+ continue;
169
+ const value = values[name];
170
+ if (value !== undefined)
171
+ images.push(value);
172
+ }
173
+ return images;
174
+ }
175
+ /**
176
+ * The harness broker.
177
+ *
178
+ * One instance holds the authorizations (which tool may use which provider generation)
179
+ * and the host policy. It is deliberately not a registry of tools: nothing here switches
180
+ * on a tool name, which is what RCP-12 forbids and what the tests assert by running two
181
+ * different recipes through the same code path.
182
+ */
183
+ export class HarnessBroker {
184
+ #grants = new Map();
185
+ #policy;
186
+ #record;
187
+ constructor(options = {}) {
188
+ this.#policy = options.policy ?? INITIAL_HOST_POLICY;
189
+ this.#record = options.record;
190
+ /**
191
+ * Refuse to exist in a state where recording would be claimed and not performed.
192
+ *
193
+ * The alternative — accepting the policy and journaling nothing — is the failure
194
+ * mode HLM-09 names: a request that must be reconstructable and is not, with nothing
195
+ * in the pipeline looking wrong.
196
+ */
197
+ if (this.#policy.requireRecording && this.#record === undefined) {
198
+ throw new Error('the host policy requires recording (HLM-09) but no journal sink was provided. Refusing to construct a broker '
199
+ + 'that would report recorded requests it never recorded; pass a sink or set requireRecording to false');
200
+ }
201
+ }
202
+ /** The host policy in force. */
203
+ get policy() {
204
+ return this.#policy;
205
+ }
206
+ /**
207
+ * Authorize a provider generation for one tool (HLM-02).
208
+ *
209
+ * The generation is CAPTURED here rather than read at dispatch time, so a provider that
210
+ * reloads between authorization and dispatch stops being authorized instead of being
211
+ * silently used under the old decision.
212
+ * @param toolName - the tool this authorization covers.
213
+ * @param provider - the provider.
214
+ */
215
+ authorize(toolName, provider) {
216
+ const grant = { toolName, provider, generation: provider.generation };
217
+ this.#grants.set(toolName, grant);
218
+ return grant;
219
+ }
220
+ /**
221
+ * Revoke a tool's authorization.
222
+ * @param toolName - the tool to revoke.
223
+ * @returns whether an authorization existed.
224
+ */
225
+ revoke(toolName) {
226
+ return this.#grants.delete(toolName);
227
+ }
228
+ /**
229
+ * The live authorization for a tool, or `undefined`.
230
+ *
231
+ * `undefined` covers both "never authorized" and "authorized for a generation this
232
+ * provider no longer is", because from a caller's point of view both mean the same
233
+ * thing: this invocation has no authorized executor. {@link resolveGrant} keeps the
234
+ * two apart for the error message.
235
+ * @param toolName - the tool to look up.
236
+ * @returns the grant, when it is still valid.
237
+ */
238
+ grantFor(toolName) {
239
+ const resolved = this.#resolveGrant(toolName);
240
+ return resolved.kind === 'ok' ? this.#grants.get(toolName) : undefined;
241
+ }
242
+ /** Resolve, keeping "missing" and "stale" distinct for the refusal reason. */
243
+ #resolveGrant(toolName) {
244
+ const grant = this.#grants.get(toolName);
245
+ if (grant === undefined)
246
+ return { kind: 'missing' };
247
+ const current = grant.provider.generation;
248
+ if (current !== grant.generation) {
249
+ return { kind: 'stale', authorized: grant.generation, current };
250
+ }
251
+ return { kind: 'ok', provider: grant.provider, generation: grant.generation };
252
+ }
253
+ /**
254
+ * Run one recipe invocation.
255
+ *
256
+ * Cancellation, policy refusals and provider failures all come back as outcomes rather
257
+ * than exceptions, so a caller that must report a result cannot lose one to an
258
+ * unhandled rejection. Use {@link runOrThrow} where an exception is the right shape.
259
+ * @param recipe - the recipe to run.
260
+ * @param args - the caller's arguments, validated against the recipe's inputs.
261
+ * @param options - the cancellation channel and deadline.
262
+ * @returns the outcome: never a success that carries no text.
263
+ */
264
+ async run(recipe, args, options = {}) {
265
+ const controller = new AbortController();
266
+ let cancelReason = 'the caller aborted the invocation';
267
+ const caller = options.signal;
268
+ const onCallerAbort = () => {
269
+ cancelReason = 'the caller aborted the invocation';
270
+ controller.abort();
271
+ };
272
+ if (caller !== undefined) {
273
+ if (caller.aborted) {
274
+ cancelReason = 'the caller aborted the invocation before it started';
275
+ controller.abort();
276
+ }
277
+ else {
278
+ caller.addEventListener('abort', onCallerAbort, { once: true });
279
+ }
280
+ }
281
+ const deadlineMs = options.deadlineMs;
282
+ const timer = deadlineMs === undefined
283
+ ? undefined
284
+ : setTimeout(() => {
285
+ cancelReason = `the invocation deadline of ${deadlineMs}ms expired`;
286
+ controller.abort();
287
+ }, deadlineMs);
288
+ try {
289
+ return await this.#invoke(recipe, args, controller.signal, () => cancelReason);
290
+ }
291
+ finally {
292
+ // Cleaning up here is not tidiness: a listener left on a long-lived signal is a
293
+ // leak per invocation, and a timer left running keeps the process alive.
294
+ if (timer !== undefined)
295
+ clearTimeout(timer);
296
+ if (caller !== undefined)
297
+ caller.removeEventListener('abort', onCallerAbort);
298
+ }
299
+ }
300
+ /**
301
+ * Run one recipe invocation, throwing on failure or cancellation.
302
+ * @param recipe - the recipe to run.
303
+ * @param args - the caller's arguments.
304
+ * @param options - the cancellation channel and deadline.
305
+ * @returns the completed outcome.
306
+ * @throws BrokerError on any failure, including cancellation.
307
+ */
308
+ async runOrThrow(recipe, args, options = {}) {
309
+ const outcome = await this.run(recipe, args, options);
310
+ if (outcome.kind === 'completed')
311
+ return outcome;
312
+ if (outcome.kind === 'cancelled')
313
+ throw new BrokerError('CANCELLED', outcome.reason);
314
+ throw new BrokerError(outcome.code, outcome.reason);
315
+ }
316
+ /** The invocation itself: checks in the order they must happen, then one dispatch. */
317
+ async #invoke(recipe, args, signal, reason) {
318
+ // Cancellation first: a cancelled call must not do any of the work below, and the
319
+ // provider must not be called at all.
320
+ if (signal.aborted)
321
+ return { kind: 'cancelled', reason: reason() };
322
+ const binding = validateRecipeInputs(recipe, args);
323
+ if (!binding.ok) {
324
+ return { kind: 'failed', code: bindingCode(binding.errors), reason: binding.errors.join('; ') };
325
+ }
326
+ const resolved = this.#resolveGrant(recipe.tool.name);
327
+ if (resolved.kind === 'missing') {
328
+ return {
329
+ kind: 'failed',
330
+ code: 'EXECUTOR_NOT_CONFIGURED',
331
+ reason: `no executor is authorized for ${recipe.tool.name}. A recipe is a request for a host-approved harness `
332
+ + 'model, not an authorization to reach one (RCP-05)',
333
+ };
334
+ }
335
+ if (resolved.kind === 'stale') {
336
+ return {
337
+ kind: 'failed',
338
+ code: 'EXECUTOR_NOT_CONFIGURED',
339
+ reason: `the authorization for ${recipe.tool.name} covers provider generation ${resolved.authorized}, but the `
340
+ + `provider now reports generation ${resolved.current}. An authorization is for a generation, not for a `
341
+ + 'name: re-authorize it rather than running new endpoint code under the old decision (HLM-02)',
342
+ };
343
+ }
344
+ const provider = resolved.provider;
345
+ /**
346
+ * ONE capability snapshot per invocation.
347
+ *
348
+ * Reading capabilities again after the call, or per check, is how one model's facts
349
+ * end up combined with another endpoint's after a concurrent switch (HLM-02).
350
+ */
351
+ const capabilities = provider.capabilities();
352
+ const values = binding.values;
353
+ const images = imagesOf(recipe, values);
354
+ if (images.length > 0) {
355
+ const imagesCapability = imageCapability(capabilities.images);
356
+ if (imagesCapability === 'unsupported') {
357
+ return {
358
+ kind: 'failed',
359
+ code: 'IMAGE_INPUT_UNAVAILABLE',
360
+ reason: `${recipe.tool.name} binds ${images.length} image input(s) but provider "${provider.id}" reports no `
361
+ + 'image support. Refusing rather than sending the text alone: a model asked to describe an image it never '
362
+ + 'received produces a fluent invention, and that is worse than an error (HLM-11)',
363
+ };
364
+ }
365
+ if (imagesCapability === 'unknown') {
366
+ return {
367
+ kind: 'failed',
368
+ code: 'EXECUTOR_CAPABILITY_UNKNOWN',
369
+ reason: `provider "${provider.id}" has not reported whether it accepts images, and ${recipe.tool.name} binds `
370
+ + 'an image input. An unevaluated capability is not a capability: refusing rather than assuming it '
371
+ + '(MODEL-06)',
372
+ };
373
+ }
374
+ }
375
+ /**
376
+ * The image ceiling is the INTERSECTION of every limit (HLM-07, RES-10).
377
+ *
378
+ * A narrowed host policy must be able to refuse images a recipe permits, which is
379
+ * the only direction a limit may move.
380
+ */
381
+ const imageCeiling = Math.min(recipe.imageInputs.max, this.#policy.imageInputs, INITIAL_MAX_IMAGE_INPUTS);
382
+ if (images.length > imageCeiling) {
383
+ return {
384
+ kind: 'failed',
385
+ code: 'MODEL_BUDGET_EXCEEDED',
386
+ reason: `${images.length} image input(s) supplied against an effective ceiling of ${imageCeiling} (recipe `
387
+ + `${recipe.imageInputs.max}, host ${this.#policy.imageInputs}, RES-10 ${INITIAL_MAX_IMAGE_INPUTS})`,
388
+ };
389
+ }
390
+ if (!permitsDestination(this.#policy, provider.destination)) {
391
+ return {
392
+ kind: 'failed',
393
+ code: 'EXECUTOR_NOT_CONFIGURED',
394
+ reason: `provider "${provider.id}" is a ${provider.destination} destination and the policy in force permits `
395
+ + 'local destinations only. A local-only profile prohibits remote destinations however the tool was built, '
396
+ + 'so this is refused rather than routed (RES-10)',
397
+ };
398
+ }
399
+ const maxOutputTokens = Math.min(recipe.maxOutputTokens, this.#policy.outputTokens, capabilities.maxOutputTokens);
400
+ if (!Number.isInteger(maxOutputTokens) || maxOutputTokens < 1) {
401
+ return {
402
+ kind: 'failed',
403
+ code: 'MODEL_BUDGET_EXCEEDED',
404
+ reason: `the effective output-token ceiling is ${maxOutputTokens} (recipe ${recipe.maxOutputTokens}, host `
405
+ + `${this.#policy.outputTokens}, provider ${capabilities.maxOutputTokens}). A call permitted to produce `
406
+ + 'nothing cannot produce a result (RES-10)',
407
+ };
408
+ }
409
+ /**
410
+ * Recording is settled BEFORE dispatch.
411
+ *
412
+ * HLM-09: if mandatory recording conflicts with policy, refuse rather than silently
413
+ * drop it. Refusing here means no provider is called, so a refused invocation is not
414
+ * a billed one.
415
+ */
416
+ if (!recipe.policy.record && this.#policy.requireRecording) {
417
+ return {
418
+ kind: 'failed',
419
+ code: 'CAPTURE_POLICY_CONFLICT',
420
+ reason: `${recipe.tool.name} declares record: false while the host requires the prepared request to be `
421
+ + 'journaled. Refusing rather than running unrecorded: a request that must be reconstructable and is not is '
422
+ + 'the failure HLM-09 names, and it leaves nothing in the pipeline looking wrong',
423
+ };
424
+ }
425
+ let recorded = false;
426
+ /**
427
+ * The request is built BEFORE the journal entry, and the entry quotes THAT request.
428
+ *
429
+ * HLM-09 asks the journal to record the exact prepared request. Rendering the prompt
430
+ * separately for the entry would make the record a second rendering that merely ought
431
+ * to agree with the one that was sent; quoting the request's own prompt makes the two
432
+ * the same string by construction.
433
+ */
434
+ const request = buildIsolatedRequest(recipe, values, maxOutputTokens, signal);
435
+ if (recipe.policy.record) {
436
+ const sink = this.#record;
437
+ if (sink === undefined) {
438
+ return {
439
+ kind: 'failed',
440
+ code: 'CAPTURE_POLICY_CONFLICT',
441
+ reason: 'recording is required but no journal sink is configured, so the prepared request would be lost',
442
+ };
443
+ }
444
+ const entry = {
445
+ kind: 'harness-model-auxiliary-call',
446
+ toolName: recipe.tool.name,
447
+ contractId: recipe.tool.contractId,
448
+ recipeId: recipe.id,
449
+ recipeVersion: recipe.version,
450
+ providerId: provider.id,
451
+ providerGeneration: resolved.generation,
452
+ destination: provider.destination,
453
+ maxOutputTokens,
454
+ imageCount: images.length,
455
+ prompt: request.prompt,
456
+ context: 'recipe-only',
457
+ };
458
+ try {
459
+ await sink(entry);
460
+ recorded = true;
461
+ }
462
+ catch (error) {
463
+ return {
464
+ kind: 'failed',
465
+ code: 'RECORDING_FAILED',
466
+ reason: `the auxiliary-call journal rejected the entry (${messageOf(error)}), so the call did not run. A call `
467
+ + 'whose record cannot be written is not one this broker will make',
468
+ };
469
+ }
470
+ }
471
+ const dispatched = await dispatch(provider, request, signal, reason);
472
+ if (dispatched.kind === 'cancelled')
473
+ return { kind: 'cancelled', reason: dispatched.reason };
474
+ if (dispatched.kind === 'failed') {
475
+ return { kind: 'failed', code: 'PROVIDER_FAILED', reason: dispatched.reason };
476
+ }
477
+ /**
478
+ * A signal aborted while the provider was working is a cancellation, whatever came
479
+ * back.
480
+ *
481
+ * Checking after the await is the difference between "we stopped asking" and "we
482
+ * stopped caring": the caller cancelled, so a result that arrived afterwards is not
483
+ * one it asked for, and reporting it as a success would attach an answer to a
484
+ * cancelled invocation (HLM-10).
485
+ */
486
+ if (signal.aborted) {
487
+ return { kind: 'cancelled', reason: `${reason()}; the provider's result was discarded rather than reported` };
488
+ }
489
+ /**
490
+ * The generation is rechecked when the call returns, and a change makes the result
491
+ * UNATTRIBUTABLE.
492
+ *
493
+ * HLM-02 forbids combining one model's capability facts with another endpoint, and a
494
+ * provider that reloads while the call is in flight leaves the broker holding a result
495
+ * it cannot honestly attribute to either generation. Reporting it under the resolved
496
+ * generation would be a receipt claiming provenance the broker does not have.
497
+ */
498
+ if (provider.generation !== resolved.generation) {
499
+ return {
500
+ kind: 'failed',
501
+ code: 'PROVIDER_FAILED',
502
+ reason: `provider "${provider.id}" changed generation from ${resolved.generation} to ${provider.generation} `
503
+ + 'while the call was in flight, so the result cannot be attributed to the endpoint this invocation '
504
+ + 'resolved. Refusing rather than reporting it under either generation (HLM-02)',
505
+ };
506
+ }
507
+ const completion = dispatched.completion;
508
+ const identity = {
509
+ provider: { id: provider.id, generation: resolved.generation, destination: provider.destination },
510
+ recipe: {
511
+ id: recipe.id,
512
+ version: recipe.version,
513
+ toolName: recipe.tool.name,
514
+ contractId: recipe.tool.contractId,
515
+ },
516
+ };
517
+ switch (completion.kind) {
518
+ case 'refused':
519
+ return {
520
+ kind: 'failed',
521
+ code: 'MODEL_REFUSED',
522
+ reason: `the model declined: ${completion.reason}. A refusal is an error and never an empty result, because a `
523
+ + 'caller writing an empty result into a document sees a blank field rather than a failure',
524
+ };
525
+ case 'truncated':
526
+ return {
527
+ kind: 'failed',
528
+ code: 'MODEL_OUTPUT_TRUNCATED',
529
+ reason: 'the generation was cut short, so no complete result was produced. Partial prose is not a successful '
530
+ + 'result (HLM-07)',
531
+ };
532
+ case 'tool_call':
533
+ return {
534
+ kind: 'failed',
535
+ code: 'UNEXPECTED_MODEL_TOOL_CALL',
536
+ reason: `the provider emitted a tool call for "${completion.toolName}". No tool catalog was supplied, so a `
537
+ + 'tool call is not executed and no agent loop continues from it (HLM-06)',
538
+ };
539
+ case 'text': {
540
+ if (completion.text.trim().length === 0) {
541
+ return {
542
+ kind: 'failed',
543
+ code: 'INVALID_MODEL_OUTPUT',
544
+ reason: 'the provider returned empty text. An empty completion is not a result: reporting it as one '
545
+ + 'produces an empty title, an empty description and no error anywhere',
546
+ };
547
+ }
548
+ return {
549
+ kind: 'completed',
550
+ text: completion.text,
551
+ usage: completion.usage ?? {
552
+ known: false,
553
+ reason: 'the provider reported no usage for this call',
554
+ },
555
+ recorded,
556
+ ...identity,
557
+ };
558
+ }
559
+ }
560
+ }
561
+ }
562
+ /**
563
+ * Dispatch one call, honouring cancellation and turning a provider throw into a failure.
564
+ * @param provider - the provider.
565
+ * @param request - the isolated request.
566
+ * @param signal - the cancellation channel.
567
+ * @param reason - reads the current cancellation reason.
568
+ * @returns how the dispatch settled.
569
+ */
570
+ async function dispatch(provider, request, signal, reason) {
571
+ if (signal.aborted)
572
+ return { kind: 'cancelled', reason: reason() };
573
+ let onAbort;
574
+ const cancelled = new Promise((resolve) => {
575
+ onAbort = () => resolve({ kind: 'cancelled', reason: reason() });
576
+ signal.addEventListener('abort', onAbort, { once: true });
577
+ });
578
+ try {
579
+ return await Promise.race([
580
+ provider.complete(request).then((completion) => ({ kind: 'completion', completion }),
581
+ // The provider's own rejection is handled here rather than escaping as an
582
+ // unhandled rejection after a cancellation already won the race.
583
+ (error) => ({ kind: 'failed', reason: `provider "${provider.id}" failed: ${messageOf(error)}` })),
584
+ cancelled,
585
+ ]);
586
+ }
587
+ finally {
588
+ if (onAbort !== undefined)
589
+ signal.removeEventListener('abort', onAbort);
590
+ }
591
+ }
592
+ //# sourceMappingURL=broker.js.map
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The recipe authoring CLI (RCP-08).
3
+ *
4
+ * Three commands, each a required new tool rather than an existing npm/DSH
5
+ * command: `validate` checks a draft against BOTH authorities (the published
6
+ * `harness-model-recipe.schema.json` shipped beside this module AND the code
7
+ * validator `parseRecipe`), `describe` prints the effective bounds an operator
8
+ * reads before deciding, and `pack` builds a declarative-extension package
9
+ * (manifest + recipes, NO executable code) consumable by the extension install
10
+ * path.
11
+ *
12
+ * Guardrails built in rather than documented:
13
+ *
14
+ * - The schema is read from THIS PACKAGE'S SHIPPED FILE, resolved relative to
15
+ * the module (so a packed install validates against the packed artifact, not
16
+ * some source checkout).
17
+ * - Packing validates the exact production artifact and runs NO lifecycle
18
+ * scripts: the output is written directly (directory + a store-method zip),
19
+ * never through `npm pack`.
20
+ * - Nothing here runs inference, installs or activates anything, or touches
21
+ * the network (RCP-08's creation rules).
22
+ * - Exit codes are a contract: 0 valid, 1 invalid (every refusal named),
23
+ * 2 usage or IO failure.
24
+ *
25
+ * RCP-09 (agent-assisted creation with an authoring grant) is NOT claimed by
26
+ * this module: the CLI is a plain argv/stdio tool, which an agent CAN drive,
27
+ * but the authoring-grant and draft-directory policy belong to the host and are
28
+ * left as the recorded remainder.
29
+ *
30
+ * @module @try-works/tool-mode-recipes/cli
31
+ */
32
+ import { type RecipeManifest } from './recipe-format.ts';
33
+ /** Where the CLI writes. Injected so a test can capture output. */
34
+ export interface RecipeCliIo {
35
+ out(line: string): void;
36
+ err(line: string): void;
37
+ }
38
+ /** The two-verdict validation of one recipe document. */
39
+ export interface RecipeVerdict {
40
+ /** The published-schema verdict. */
41
+ readonly schemaOk: boolean;
42
+ /** Rendered schema violations, one per refusal. */
43
+ readonly schemaViolations: readonly string[];
44
+ /** The code-validator verdict. */
45
+ readonly codeOk: boolean;
46
+ /** The code validator's rendered refusals. */
47
+ readonly codeViolations: readonly string[];
48
+ /** The parsed recipe, when the code validator admits the document. */
49
+ readonly recipe?: RecipeManifest;
50
+ }
51
+ /** Validate one decoded document against BOTH authorities (RCP-02, RCP-08). */
52
+ export declare function judgeRecipe(document: unknown): RecipeVerdict;
53
+ /**
54
+ * Run one CLI invocation.
55
+ * @param argv - arguments after the executable name.
56
+ * @param io - the output sinks.
57
+ * @returns a promise of the process exit code.
58
+ */
59
+ export declare function runRecipeCli(argv: readonly string[], io: RecipeCliIo): Promise<number>;
60
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAoBH,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAA;AAe3B,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AA8BD,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,mDAAmD;IACnD,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5C,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,8CAA8C;IAC9C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAA;CACjC;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa,CAW5D;AAyED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5F"}