@harperfast/harper 5.3.0-beta.3 → 5.3.0-beta.4

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 (311) hide show
  1. package/bin/cliOperations.ts +25 -3
  2. package/bin/run.ts +18 -0
  3. package/components/Application.ts +52 -78
  4. package/components/DESIGN.md +76 -3
  5. package/components/Scope.ts +10 -4
  6. package/components/anthropic/index.ts +10 -1
  7. package/components/bedrock/index.ts +10 -1
  8. package/components/componentLoader.ts +23 -14
  9. package/components/mcp/tools/operations.ts +3 -1
  10. package/components/ollama/index.ts +10 -1
  11. package/components/openai/index.ts +265 -18
  12. package/components/operations.js +40 -1
  13. package/config/configUtils.ts +3 -19
  14. package/dataLayer/DESIGN.md +29 -5
  15. package/dataLayer/harperBridge/ResourceBridge.ts +39 -3
  16. package/dataLayer/restoreMarker.ts +250 -14
  17. package/dataLayer/schema.ts +2 -7
  18. package/dataLayer/schemaDescribe.ts +10 -0
  19. package/dist/bin/cliOperations.js +28 -3
  20. package/dist/bin/cliOperations.js.map +1 -1
  21. package/dist/bin/run.js +19 -0
  22. package/dist/bin/run.js.map +1 -1
  23. package/dist/components/Application.d.ts +8 -2
  24. package/dist/components/Application.js +43 -69
  25. package/dist/components/Application.js.map +1 -1
  26. package/dist/components/Scope.d.ts +1 -1
  27. package/dist/components/Scope.js +9 -4
  28. package/dist/components/Scope.js.map +1 -1
  29. package/dist/components/anthropic/index.d.ts +0 -10
  30. package/dist/components/anthropic/index.js +9 -1
  31. package/dist/components/anthropic/index.js.map +1 -1
  32. package/dist/components/bedrock/index.d.ts +0 -12
  33. package/dist/components/bedrock/index.js +9 -1
  34. package/dist/components/bedrock/index.js.map +1 -1
  35. package/dist/components/componentLoader.js +23 -14
  36. package/dist/components/componentLoader.js.map +1 -1
  37. package/dist/components/mcp/tools/operations.js +1 -1
  38. package/dist/components/mcp/tools/operations.js.map +1 -1
  39. package/dist/components/ollama/index.d.ts +0 -13
  40. package/dist/components/ollama/index.js +9 -1
  41. package/dist/components/ollama/index.js.map +1 -1
  42. package/dist/components/openai/index.d.ts +11 -15
  43. package/dist/components/openai/index.js +224 -12
  44. package/dist/components/openai/index.js.map +1 -1
  45. package/dist/components/operations.d.ts +9 -0
  46. package/dist/components/operations.js +32 -2
  47. package/dist/components/operations.js.map +1 -1
  48. package/dist/config/configUtils.d.ts +1 -2
  49. package/dist/config/configUtils.js +7 -22
  50. package/dist/config/configUtils.js.map +1 -1
  51. package/dist/dataLayer/harperBridge/ResourceBridge.js +29 -2
  52. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  53. package/dist/dataLayer/restoreMarker.d.ts +25 -3
  54. package/dist/dataLayer/restoreMarker.js +213 -13
  55. package/dist/dataLayer/restoreMarker.js.map +1 -1
  56. package/dist/dataLayer/schema.js +1 -4
  57. package/dist/dataLayer/schema.js.map +1 -1
  58. package/dist/dataLayer/schemaDescribe.js +10 -0
  59. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/json/systemSchema.json +28 -0
  63. package/dist/resources/DatabaseTransaction.d.ts +21 -1
  64. package/dist/resources/DatabaseTransaction.js +147 -8
  65. package/dist/resources/DatabaseTransaction.js.map +1 -1
  66. package/dist/resources/PrimaryRocksDatabase.js +1 -1
  67. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  68. package/dist/resources/Table.d.ts +31 -3
  69. package/dist/resources/Table.js +397 -79
  70. package/dist/resources/Table.js.map +1 -1
  71. package/dist/resources/auditStore.js +16 -10
  72. package/dist/resources/auditStore.js.map +1 -1
  73. package/dist/resources/blob.d.ts +1 -0
  74. package/dist/resources/blob.js +14 -5
  75. package/dist/resources/blob.js.map +1 -1
  76. package/dist/resources/branchDatabase.js +3 -3
  77. package/dist/resources/branchDatabase.js.map +1 -1
  78. package/dist/resources/databaseDropPreparation.d.ts +20 -0
  79. package/dist/resources/databaseDropPreparation.js +106 -0
  80. package/dist/resources/databaseDropPreparation.js.map +1 -0
  81. package/dist/resources/databases.d.ts +26 -21
  82. package/dist/resources/databases.js +1040 -204
  83. package/dist/resources/databases.js.map +1 -1
  84. package/dist/resources/derivedIndexRegistry.d.ts +23 -0
  85. package/dist/resources/derivedIndexRegistry.js +92 -0
  86. package/dist/resources/derivedIndexRegistry.js.map +1 -1
  87. package/dist/resources/derivedIndexRuntime.d.ts +10 -0
  88. package/dist/resources/derivedIndexRuntime.js +136 -25
  89. package/dist/resources/derivedIndexRuntime.js.map +1 -1
  90. package/dist/resources/derivedIndexes.d.ts +39 -0
  91. package/dist/resources/derivedIndexes.js +733 -0
  92. package/dist/resources/derivedIndexes.js.map +1 -0
  93. package/dist/resources/fullTextSchema.d.ts +8 -0
  94. package/dist/resources/fullTextSchema.js +42 -0
  95. package/dist/resources/fullTextSchema.js.map +1 -1
  96. package/dist/resources/fullTextSchemaLifecycle.d.ts +0 -5
  97. package/dist/resources/fullTextSchemaLifecycle.js +0 -9
  98. package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
  99. package/dist/resources/graphql.js +101 -9
  100. package/dist/resources/graphql.js.map +1 -1
  101. package/dist/resources/indexes/hnswDerivedIndex.d.ts +0 -10
  102. package/dist/resources/indexes/hnswDerivedIndex.js +0 -230
  103. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -1
  104. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +3 -0
  105. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +57 -8
  106. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
  107. package/dist/resources/models/Models.d.ts +26 -3
  108. package/dist/resources/models/Models.js +256 -13
  109. package/dist/resources/models/Models.js.map +1 -1
  110. package/dist/resources/models/TestBackend.d.ts +3 -1
  111. package/dist/resources/models/TestBackend.js +35 -1
  112. package/dist/resources/models/TestBackend.js.map +1 -1
  113. package/dist/resources/models/analyticsTable.d.ts +2 -2
  114. package/dist/resources/models/analyticsTable.js +4 -2
  115. package/dist/resources/models/analyticsTable.js.map +1 -1
  116. package/dist/resources/models/backendHelpers.d.ts +24 -0
  117. package/dist/resources/models/backendHelpers.js +18 -1
  118. package/dist/resources/models/backendHelpers.js.map +1 -1
  119. package/dist/resources/models/backendRegistry.d.ts +10 -21
  120. package/dist/resources/models/backendRegistry.js +74 -18
  121. package/dist/resources/models/backendRegistry.js.map +1 -1
  122. package/dist/resources/models/bootstrap.d.ts +7 -0
  123. package/dist/resources/models/bootstrap.js +36 -4
  124. package/dist/resources/models/bootstrap.js.map +1 -1
  125. package/dist/resources/models/decideHook.d.ts +33 -0
  126. package/dist/resources/models/decideHook.js +105 -0
  127. package/dist/resources/models/decideHook.js.map +1 -0
  128. package/dist/resources/models/decision.d.ts +64 -0
  129. package/dist/resources/models/decision.js +507 -0
  130. package/dist/resources/models/decision.js.map +1 -0
  131. package/dist/resources/models/decisionStore.d.ts +134 -0
  132. package/dist/resources/models/decisionStore.js +350 -0
  133. package/dist/resources/models/decisionStore.js.map +1 -0
  134. package/dist/resources/models/embedHook.d.ts +66 -8
  135. package/dist/resources/models/embedHook.js +228 -61
  136. package/dist/resources/models/embedHook.js.map +1 -1
  137. package/dist/resources/models/generativeDecision.d.ts +58 -0
  138. package/dist/resources/models/generativeDecision.js +315 -0
  139. package/dist/resources/models/generativeDecision.js.map +1 -0
  140. package/dist/resources/models/types.d.ts +232 -4
  141. package/dist/security/authn/oidc/tokenExchange.js +3 -33
  142. package/dist/security/authn/oidc/tokenExchange.js.map +1 -1
  143. package/dist/security/authn/oidc/tokenUseTable.d.ts +9 -0
  144. package/dist/security/authn/oidc/tokenUseTable.js +28 -0
  145. package/dist/security/authn/oidc/tokenUseTable.js.map +1 -0
  146. package/dist/security/certificateVerification/certificateVerificationSource.d.ts +0 -1
  147. package/dist/security/certificateVerification/certificateVerificationSource.js +3 -3
  148. package/dist/security/certificateVerification/certificateVerificationSource.js.map +1 -1
  149. package/dist/security/certificateVerification/crlVerification.js +77 -165
  150. package/dist/security/certificateVerification/crlVerification.js.map +1 -1
  151. package/dist/security/certificateVerification/types.d.ts +0 -3
  152. package/dist/security/certificateVerification/verificationTables.d.ts +19 -0
  153. package/dist/security/certificateVerification/verificationTables.js +125 -0
  154. package/dist/security/certificateVerification/verificationTables.js.map +1 -0
  155. package/dist/security/certificateVerification/verificationUtils.d.ts +1 -1
  156. package/dist/security/certificateVerification/verificationUtils.js +6 -31
  157. package/dist/security/certificateVerification/verificationUtils.js.map +1 -1
  158. package/dist/security/jsLoader.d.ts +5 -0
  159. package/dist/security/jsLoader.js +8 -0
  160. package/dist/security/jsLoader.js.map +1 -1
  161. package/dist/server/Server.d.ts +1 -0
  162. package/dist/server/Server.js.map +1 -1
  163. package/dist/server/http.js +2 -2
  164. package/dist/server/http.js.map +1 -1
  165. package/dist/server/itc/serverHandlers.js +21 -2
  166. package/dist/server/itc/serverHandlers.js.map +1 -1
  167. package/dist/server/jobs/jobOwnership.d.ts +25 -0
  168. package/dist/server/jobs/jobOwnership.js +127 -0
  169. package/dist/server/jobs/jobOwnership.js.map +1 -0
  170. package/dist/server/jobs/jobProcess.js +9 -2
  171. package/dist/server/jobs/jobProcess.js.map +1 -1
  172. package/dist/server/jobs/jobs.js +18 -5
  173. package/dist/server/jobs/jobs.js.map +1 -1
  174. package/dist/server/loadRootComponents.js +10 -0
  175. package/dist/server/loadRootComponents.js.map +1 -1
  176. package/dist/server/serverHelpers/contentTypes.js +11 -1
  177. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  178. package/dist/server/serverHelpers/serverHandlers.js +12 -2
  179. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  180. package/dist/server/threads/itc.d.ts +4 -1
  181. package/dist/server/threads/itc.js +29 -6
  182. package/dist/server/threads/itc.js.map +1 -1
  183. package/dist/server/threads/manageThreads.d.ts +12 -4
  184. package/dist/server/threads/manageThreads.js +241 -119
  185. package/dist/server/threads/manageThreads.js.map +1 -1
  186. package/dist/server/threads/threadServer.d.ts +1 -0
  187. package/dist/server/threads/threadServer.js +15 -1
  188. package/dist/server/threads/threadServer.js.map +1 -1
  189. package/dist/server/threads/windowsProcessTree.d.ts +91 -0
  190. package/dist/server/threads/windowsProcessTree.js +361 -0
  191. package/dist/server/threads/windowsProcessTree.js.map +1 -0
  192. package/dist/server/throttle.d.ts +1 -1
  193. package/dist/server/throttle.js +8 -1
  194. package/dist/server/throttle.js.map +1 -1
  195. package/dist/upgrade/directives/5-3-0.js +53 -46
  196. package/dist/upgrade/directives/5-3-0.js.map +1 -1
  197. package/dist/utility/errors/hdbError.d.ts +10 -1
  198. package/dist/utility/errors/hdbError.js +55 -11
  199. package/dist/utility/errors/hdbError.js.map +1 -1
  200. package/dist/utility/fsync.d.ts +2 -0
  201. package/dist/utility/fsync.js +20 -0
  202. package/dist/utility/fsync.js.map +1 -0
  203. package/dist/utility/globalSchema.d.ts +18 -0
  204. package/dist/utility/hdbTerms.d.ts +3 -0
  205. package/dist/utility/hdbTerms.js +3 -0
  206. package/dist/utility/hdbTerms.js.map +1 -1
  207. package/dist/utility/signalling.d.ts +7 -1
  208. package/dist/utility/signalling.js +27 -3
  209. package/dist/utility/signalling.js.map +1 -1
  210. package/dist/utility/when.d.ts +1 -0
  211. package/dist/utility/when.js +21 -0
  212. package/dist/utility/when.js.map +1 -1
  213. package/dist/validation/configValidator.js +30 -3
  214. package/dist/validation/configValidator.js.map +1 -1
  215. package/index.ts +13 -0
  216. package/json/systemSchema.json +28 -0
  217. package/npm-shrinkwrap.json +151 -55
  218. package/package.json +14 -13
  219. package/resources/DESIGN.md +80 -100
  220. package/resources/DatabaseTransaction.ts +157 -10
  221. package/resources/PrimaryRocksDatabase.ts +2 -2
  222. package/resources/Table.ts +494 -101
  223. package/resources/auditStore.ts +18 -10
  224. package/resources/blob.ts +13 -6
  225. package/resources/branchDatabase.ts +3 -3
  226. package/resources/databaseDropPreparation.ts +120 -0
  227. package/resources/databases.ts +1155 -205
  228. package/resources/derivedIndexRegistry.ts +122 -0
  229. package/resources/derivedIndexRuntime.ts +157 -30
  230. package/resources/derivedIndexes.ts +856 -0
  231. package/resources/fullTextSchema.ts +55 -0
  232. package/resources/fullTextSchemaLifecycle.ts +0 -15
  233. package/resources/graphql.ts +107 -12
  234. package/resources/indexes/DESIGN.md +7 -0
  235. package/resources/indexes/hnswDerivedIndex.ts +0 -260
  236. package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +72 -9
  237. package/resources/models/DESIGN.md +39 -0
  238. package/resources/models/Models.ts +361 -18
  239. package/resources/models/TestBackend.ts +56 -1
  240. package/resources/models/analyticsTable.ts +6 -4
  241. package/resources/models/backendHelpers.ts +17 -0
  242. package/resources/models/backendRegistry.ts +76 -21
  243. package/resources/models/bootstrap.ts +57 -7
  244. package/resources/models/decideHook.ts +135 -0
  245. package/resources/models/decision.ts +505 -0
  246. package/resources/models/decisionStore.ts +419 -0
  247. package/resources/models/embedHook.ts +272 -55
  248. package/resources/models/generativeDecision.ts +459 -0
  249. package/resources/models/types.ts +231 -4
  250. package/security/DESIGN.md +22 -2
  251. package/security/authn/oidc/tokenExchange.ts +6 -34
  252. package/security/authn/oidc/tokenUseTable.ts +27 -0
  253. package/security/certificateVerification/certificateVerificationSource.ts +2 -3
  254. package/security/certificateVerification/crlVerification.ts +96 -174
  255. package/security/certificateVerification/types.ts +0 -3
  256. package/security/certificateVerification/verificationTables.ts +124 -0
  257. package/security/certificateVerification/verificationUtils.ts +8 -32
  258. package/security/jsLoader.ts +9 -1
  259. package/server/DESIGN.md +27 -1
  260. package/server/Server.ts +2 -1
  261. package/server/http.ts +4 -2
  262. package/server/itc/serverHandlers.js +29 -1
  263. package/server/jobs/jobOwnership.ts +89 -0
  264. package/server/jobs/jobProcess.ts +8 -2
  265. package/server/jobs/jobs.ts +15 -4
  266. package/server/loadRootComponents.js +11 -0
  267. package/server/serverHelpers/contentTypes.ts +13 -1
  268. package/server/serverHelpers/serverHandlers.js +11 -2
  269. package/server/threads/itc.js +32 -6
  270. package/server/threads/manageThreads.js +268 -122
  271. package/server/threads/threadServer.js +13 -1
  272. package/server/threads/windowsProcessTree.ts +409 -0
  273. package/server/throttle.ts +11 -1
  274. package/studio/web/assets/{Chat-CveGbxe6.js → Chat-Bq60AO-g.js} +1 -1
  275. package/studio/web/assets/{FloatingChat-IpCFgYWL.js → FloatingChat-D4560T0p.js} +3 -3
  276. package/studio/web/assets/{apiToken-CS8fFTgL.js → apiToken-BEvbFPMc.js} +1 -1
  277. package/studio/web/assets/{applications-D7F4Jopl.js → applications-CSUVTH4E.js} +1 -1
  278. package/studio/web/assets/{cssMode-Daw_oLPq.js → cssMode-DJyc9mHj.js} +1 -1
  279. package/studio/web/assets/{editor-B3Bs1zWX.js → editor-DDIsQfj6.js} +1 -1
  280. package/studio/web/assets/{html-CDaQwWiE.js → html-g1BJHxLy.js} +1 -1
  281. package/studio/web/assets/{htmlMode-Cb6emO4U.js → htmlMode-Vh5IglCQ.js} +1 -1
  282. package/studio/web/assets/{index-B4g2Fu_2.js → index-DGRAzvO4.js} +5 -5
  283. package/studio/web/assets/{index.lazy-NwhvF1q1.js → index.lazy-vgSic7et.js} +1 -1
  284. package/studio/web/assets/{javascript-2_86gB9Q.js → javascript-5VIolqf7.js} +1 -1
  285. package/studio/web/assets/{jsonMode-DX-i86uQ.js → jsonMode-DG4zQraI.js} +1 -1
  286. package/studio/web/assets/{languageServices-B85WN-Ej.js → languageServices-t5FBpX-i.js} +1 -1
  287. package/studio/web/assets/{lspLanguageFeatures-JCWHIsKg.js → lspLanguageFeatures-DgVnNPh-.js} +1 -1
  288. package/studio/web/assets/{notifications-D9ejzt52.js → notifications-BeWcX04m.js} +1 -1
  289. package/studio/web/assets/{notifications-B7hjKhai.js → notifications-DUGWrWoO.js} +1 -1
  290. package/studio/web/assets/{profile-D8B6No3F.js → profile-BvUEDBzW.js} +1 -1
  291. package/studio/web/assets/{regions-DFazIfTK.js → regions-CB-_2m7M.js} +1 -1
  292. package/studio/web/assets/{register-CwFLviLF.js → register-1w1Zr3b2.js} +2 -2
  293. package/studio/web/assets/{setComponentFile-VnJ80SSJ.js → setComponentFile-z4-qJCbA.js} +1 -1
  294. package/studio/web/assets/{setup-BYQxFy-C.js → setup-CS50uJ4x.js} +2 -2
  295. package/studio/web/assets/{status-Cn5XjmsL.js → status-CI0wOdOE.js} +1 -1
  296. package/studio/web/assets/{toggleHighContrast-g9xIh6TX.js → toggleHighContrast-BsAE4jPe.js} +1 -1
  297. package/studio/web/assets/{tsMode-EC-n0Wqc.js → tsMode-D83Gg3cq.js} +1 -1
  298. package/studio/web/assets/{typescript-DHmM9JCO.js → typescript-Dfp-GrNq.js} +1 -1
  299. package/studio/web/assets/{useEntityRestURL-BUdnVSVv.js → useEntityRestURL-BKetlINf.js} +1 -1
  300. package/studio/web/assets/{workers-BVl9bBB-.js → workers-CCvmDLH3.js} +1 -1
  301. package/studio/web/assets/{xml-C4_sqOkX.js → xml-06pZp00U.js} +1 -1
  302. package/studio/web/assets/{yaml-CVkbDqtW.js → yaml-iSnWMVlV.js} +1 -1
  303. package/studio/web/index.html +1 -1
  304. package/upgrade/directives/5-3-0.ts +61 -54
  305. package/utility/DESIGN.md +5 -1
  306. package/utility/errors/hdbError.ts +56 -8
  307. package/utility/fsync.ts +16 -0
  308. package/utility/hdbTerms.ts +3 -0
  309. package/utility/signalling.ts +33 -4
  310. package/utility/when.ts +22 -0
  311. package/validation/configValidator.ts +36 -7
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Built-in `decision` backend over a configured generative logical name (#2779). A call either
3
+ * scores every allowed value from the generative model's own likelihoods (#2838), when that name
4
+ * routes to a backend implementing `scoreChoices`, or asks the model for `samples`
5
+ * structured-output completions and reports the vote frequencies as the distribution. Configured
6
+ * as `models.decision.<name>: { backend: generative, scoring: auto | vote | score }`.
7
+ *
8
+ * Every sample and every scoring call flows through the `models` facade, so each one writes its
9
+ * own `hdb_model_calls` row and token metrics; the decide row itself reports no usage, or the
10
+ * tokens would be counted twice (the same reason the agent loop's outer call writes no row).
11
+ */
12
+ import { ServerError } from '../../utility/errors/hdbError.ts';
13
+ import { composeSignal } from './backendHelpers.ts';
14
+ import { setDecision } from './backendRegistry.ts';
15
+ import {
16
+ allowedValues,
17
+ isObjectSchema,
18
+ parseDecisionSample,
19
+ type SamplePair,
20
+ stateToText,
21
+ toResponseSchema,
22
+ wantsNoMatch,
23
+ } from './decision.ts';
24
+ import { models, scoringLimit } from './Models.ts';
25
+ import { getRouter } from './routing.ts';
26
+ import type {
27
+ BackendOpts,
28
+ ChoiceScores,
29
+ Capability,
30
+ DecideInput,
31
+ DecideOpts,
32
+ DecisionLeaf,
33
+ DecisionOutcome,
34
+ DecisionOutput,
35
+ DecisionSchema,
36
+ GenerateInput,
37
+ GenerateOpts,
38
+ GenerateResult,
39
+ ModelBackend,
40
+ ModelCallResult,
41
+ ModelCapabilities,
42
+ ModelKind,
43
+ ScoreChoicesOpts,
44
+ } from './types.ts';
45
+
46
+ export type ScoringMode = 'auto' | 'vote' | 'score';
47
+
48
+ export interface GenerativeDecisionConfig {
49
+ /** Logical name of the generative model to sample or score; default `'default'`. */
50
+ generative?: string;
51
+ /** Completions per voted decision (1..25); default 5. */
52
+ samples?: number;
53
+ /** Completions or scoring calls in flight at once (1..25); default 5. */
54
+ concurrency?: number;
55
+ temperature?: number;
56
+ /** Budget for the whole decision, composed with the caller's signal. */
57
+ requestTimeoutMs?: number;
58
+ /**
59
+ * `auto` (default) scores when the generative name routes to a backend that implements
60
+ * `scoreChoices` and votes otherwise, also when scoring proves unsupported for a call; `score`
61
+ * never votes; `vote` never scores, which makes it the rollback setting.
62
+ */
63
+ scoring?: ScoringMode;
64
+ /**
65
+ * Route each sample only to a generative candidate that sends the response schema as a
66
+ * decoding constraint (`structuredOutput`); default `true`. `false` samples prompt-only
67
+ * backends, whose replies are parsed leniently and fail the decision when no in-schema
68
+ * answer is found.
69
+ */
70
+ requireStructuredOutput?: boolean;
71
+ }
72
+
73
+ export const DEFAULT_SAMPLES = 5;
74
+ export const MAX_SAMPLES = 25;
75
+ const DEFAULT_CONCURRENCY = 5;
76
+ const STRUCTURED_OUTPUT: readonly Capability[] = Object.freeze(['structuredOutput'] as Capability[]);
77
+
78
+ const CAPABILITIES: ModelCapabilities = Object.freeze({
79
+ embed: false,
80
+ generate: false,
81
+ stream: false,
82
+ tools: false,
83
+ adapters: false,
84
+ decide: true,
85
+ calibrated: false,
86
+ noMatch: true,
87
+ calibratedNoMatch: false,
88
+ });
89
+
90
+ const VOTE_SYSTEM_PROMPT =
91
+ 'You make a decision about the input. Reply with a single JSON object that matches the required schema, choosing only from the allowed values.';
92
+ const SCORE_SYSTEM_PROMPT = 'You make a decision about the input, choosing only from the allowed values.';
93
+ const SCORE_NO_MATCH_SYSTEM_PROMPT =
94
+ 'You make a decision about the input. Choose the allowed value that fits it, or the final option when none of the allowed values fits.';
95
+ const NO_MATCH_CHOICE = '(none of the listed values)';
96
+
97
+ export type GenerateFn = (input: GenerateInput, opts: GenerateOpts) => Promise<GenerateResult>;
98
+ export type ScoreFn = (
99
+ input: GenerateInput,
100
+ choices: readonly string[],
101
+ opts: ScoreChoicesOpts
102
+ ) => Promise<ChoiceScores>;
103
+
104
+ /** The facade calls the adapter makes; injectable for tests. */
105
+ export interface GenerativeDecisionDeps {
106
+ generate?: GenerateFn;
107
+ score?: ScoreFn;
108
+ /** Whether the generative name currently routes to a backend that scores; `auto` asks before every decision. */
109
+ canScore?: (logicalName: string, largestLeaf: number) => boolean;
110
+ }
111
+
112
+ export class GenerativeDecisionError extends ServerError {
113
+ constructor(message: string) {
114
+ super(message);
115
+ this.name = 'GenerativeDecisionError';
116
+ }
117
+ }
118
+
119
+ export function registerGenerativeDecisionBackend(args: {
120
+ logicalName: string;
121
+ kind: ModelKind;
122
+ config: GenerativeDecisionConfig;
123
+ }): void {
124
+ if (args.kind !== 'decision')
125
+ throw new GenerativeDecisionError(`backend 'generative' serves models.decision entries, not models.${args.kind}`);
126
+ setDecision(args.logicalName, createGenerativeDecisionBackend(args.config));
127
+ }
128
+
129
+ export function createGenerativeDecisionBackend(
130
+ config: GenerativeDecisionConfig = {},
131
+ deps: GenerativeDecisionDeps = {}
132
+ ): ModelBackend {
133
+ const generate = deps.generate ?? ((input, opts) => models.generate(input, opts));
134
+ const score = deps.score ?? ((input, choices, opts) => models.scoreChoices(input, choices, opts));
135
+ const canScore = deps.canScore ?? routesToScorer;
136
+ const logicalName = config.generative ?? 'default';
137
+ const samples = boundedCount(config.samples, DEFAULT_SAMPLES);
138
+ const concurrency = boundedCount(config.concurrency, DEFAULT_CONCURRENCY);
139
+ const scoring = config.scoring ?? 'auto';
140
+ const { temperature, requestTimeoutMs } = config;
141
+
142
+ const vote = async (
143
+ state: DecideInput,
144
+ schema: DecisionSchema,
145
+ instructions: string | undefined,
146
+ signal: AbortSignal | undefined
147
+ ): Promise<DecisionOutput<unknown>> => {
148
+ const input = buildInput(
149
+ state,
150
+ schema,
151
+ instructions,
152
+ wantsNoMatch(schema) ? `${VOTE_SYSTEM_PROMPT} ${voteNoMatchLine(schema)}` : VOTE_SYSTEM_PROMPT,
153
+ undefined
154
+ );
155
+ const responseFormat = { schema: toResponseSchema(schema) };
156
+ const votes = await runPool(
157
+ Array.from({ length: samples }, () => async (sampleSignal: AbortSignal) => {
158
+ const result = await generate(input, {
159
+ model: logicalName,
160
+ responseFormat,
161
+ temperature,
162
+ signal: sampleSignal,
163
+ requires,
164
+ });
165
+ return parseSample(schema, result.content, logicalName);
166
+ }),
167
+ concurrency,
168
+ signal
169
+ );
170
+ return tally(schema, votes);
171
+ };
172
+
173
+ const scoreLeaf = async (
174
+ state: DecideInput,
175
+ schema: DecisionSchema,
176
+ field: string | undefined,
177
+ leaf: DecisionLeaf,
178
+ instructions: string | undefined,
179
+ signal: AbortSignal
180
+ ): Promise<LeafScore> => {
181
+ const values = allowedValues(leaf);
182
+ const choices = values.map((value) => String(value));
183
+ if (leaf.noMatch !== true) {
184
+ const input = buildInput(state, schema, instructions, SCORE_SYSTEM_PROMPT, field);
185
+ const scored = await score(input, choices, { model: logicalName, signal });
186
+ return { distribution: softmax(values, scored?.logLikelihoods, logicalName) };
187
+ }
188
+ const input = buildInput(state, schema, instructions, SCORE_NO_MATCH_SYSTEM_PROMPT, field);
189
+ const scored = await score(input, [...choices, noMatchChoice(choices)], { model: logicalName, signal });
190
+ const all = softmax([...values, NO_MATCH], scored?.logLikelihoods, logicalName);
191
+ // Normalized over the allowed values' own scores, not by dividing out the none share, which can underflow to zero.
192
+ const distribution = softmax(values, (scored!.logLikelihoods as number[]).slice(0, values.length), logicalName);
193
+ return { distribution, noMatch: all[values.length].probability };
194
+ };
195
+
196
+ // One scoring call per leaf, through the same pool and budget as voting, so a 32-field schema
197
+ // does not open 32 provider connections at once and a caller abort settles every sibling.
198
+ const scoreAll = async (
199
+ state: DecideInput,
200
+ schema: DecisionSchema,
201
+ instructions: string | undefined,
202
+ signal: AbortSignal | undefined
203
+ ): Promise<DecisionOutput<unknown>> => {
204
+ if (!isObjectSchema(schema)) {
205
+ const [scored] = await runPool(
206
+ [(leafSignal: AbortSignal) => scoreLeaf(state, schema, undefined, schema, instructions, leafSignal)],
207
+ concurrency,
208
+ signal
209
+ );
210
+ return scored;
211
+ }
212
+ const entries = Object.entries(schema.properties);
213
+ const marginals = await runPool(
214
+ entries.map(
215
+ ([name, leaf]) =>
216
+ (leafSignal: AbortSignal) =>
217
+ scoreLeaf(state, schema, name, leaf, instructions, leafSignal)
218
+ ),
219
+ concurrency,
220
+ signal
221
+ );
222
+ return { fields: Object.fromEntries(entries.map(([name], i) => [name, marginals[i]])) };
223
+ };
224
+
225
+ const signatureFor = (mode: ScoringMode) =>
226
+ `generative=${logicalName};mode=${mode};samples=${samples};temperature=${temperature ?? 'default'}`;
227
+
228
+ const requires = config.requireStructuredOutput === false ? undefined : (STRUCTURED_OUTPUT as Capability[]);
229
+ return {
230
+ name: 'generative',
231
+ capabilities: () => CAPABILITIES,
232
+ async decide(
233
+ state: DecideInput,
234
+ schema: DecisionSchema,
235
+ opts: BackendOpts<DecideOpts>
236
+ ): Promise<ModelCallResult<DecisionOutput<unknown>>> {
237
+ const signal = composeSignal(opts.signal, requestTimeoutMs);
238
+ const mode: ScoringMode =
239
+ scoring === 'auto' ? (canScore(logicalName, largestLeaf(schema)) ? 'score' : 'vote') : scoring;
240
+ if (mode === 'score') {
241
+ try {
242
+ return {
243
+ status: 'completed',
244
+ output: { ...(await scoreAll(state, schema, opts.instructions, signal)), signature: signatureFor('score') },
245
+ };
246
+ } catch (err) {
247
+ if (scoring !== 'auto' || !isScoringUnsupported(err)) throw err;
248
+ }
249
+ }
250
+ return {
251
+ status: 'completed',
252
+ output: { ...(await vote(state, schema, opts.instructions, signal)), signature: signatureFor('vote') },
253
+ };
254
+ },
255
+ };
256
+ }
257
+
258
+ // A candidate that advertises scoring without implementing it counts as able, so the facade reports
259
+ // that contract failure instead of the decision quietly voting.
260
+ function routesToScorer(logicalName: string, largestLeafSize: number): boolean {
261
+ for (const backend of getRouter().route({ kind: 'generative', logicalName, requires: ['scoreChoices'] })) {
262
+ const limit = scoringLimit(backend.capabilities());
263
+ if (limit === undefined || largestLeafSize <= limit || typeof backend.scoreChoices !== 'function') return true;
264
+ }
265
+ return false;
266
+ }
267
+
268
+ // Infinity for anything that is not a well-formed leaf, so an unexpected shape never reads as within a limit.
269
+ function leafSize(leaf: DecisionLeaf): number {
270
+ if (!leaf || typeof leaf !== 'object') return Infinity;
271
+ if (Array.isArray((leaf as { enum?: unknown }).enum)) return (leaf as { enum: unknown[] }).enum.length;
272
+ if ((leaf as { type?: unknown }).type === 'boolean') return 2;
273
+ const { type, minimum, maximum } = leaf as { type?: unknown; minimum?: unknown; maximum?: unknown };
274
+ if (type !== 'integer' || !Number.isSafeInteger(minimum) || !Number.isSafeInteger(maximum)) return Infinity;
275
+ const size = (maximum as number) - (minimum as number) + 1;
276
+ return size > 0 ? size : Infinity;
277
+ }
278
+
279
+ // A leaf that opted into no-match is scored with one extra choice, the "none" continuation.
280
+ function scoredChoices(leaf: DecisionLeaf): number {
281
+ return leafSize(leaf) + ((leaf as { noMatch?: unknown } | undefined)?.noMatch === true ? 1 : 0);
282
+ }
283
+
284
+ function largestLeaf(schema: DecisionSchema): number {
285
+ if (!isObjectSchema(schema)) return scoredChoices(schema);
286
+ let largest = 0;
287
+ for (const name in schema.properties)
288
+ if (Object.hasOwn(schema.properties, name)) largest = Math.max(largest, scoredChoices(schema.properties[name]));
289
+ return largest;
290
+ }
291
+
292
+ /** The errors after which `auto` votes: the backend declined this call, or lost the capability since the probe. */
293
+ function isScoringUnsupported(err: unknown): boolean {
294
+ const name = (err as { name?: string } | null)?.name;
295
+ return name === 'ChoiceScoringUnsupportedError' || name === 'ModelCapabilityError';
296
+ }
297
+
298
+ /**
299
+ * Run `tasks` with at most `concurrency` in flight under one AbortController. The first failure
300
+ * aborts the rest, and the pool settles before that failure is thrown, so no task rejects
301
+ * unobserved and the facade never falls back to another candidate while calls are in flight.
302
+ * Siblings are aborted with a fresh reason, not the failure itself: a call that was cut short is
303
+ * recorded as aborted, not as a second copy of the failure with the same billed usage.
304
+ */
305
+ async function runPool<T>(
306
+ tasks: ReadonlyArray<(signal: AbortSignal) => Promise<T>>,
307
+ concurrency: number,
308
+ signal: AbortSignal | undefined
309
+ ): Promise<T[]> {
310
+ const controller = new AbortController();
311
+ const onAbort = () => controller.abort(signal?.reason);
312
+ if (signal?.aborted) onAbort();
313
+ else signal?.addEventListener('abort', onAbort, { once: true });
314
+ const results: T[] = new Array(tasks.length);
315
+ let failure: unknown;
316
+ let failed = false;
317
+ let next = 0;
318
+ const worker = async (): Promise<void> => {
319
+ while (next < tasks.length && !controller.signal.aborted) {
320
+ const index = next++;
321
+ try {
322
+ results[index] = await tasks[index](controller.signal);
323
+ } catch (err) {
324
+ if (!failed) {
325
+ failed = true;
326
+ failure = err;
327
+ }
328
+ controller.abort(new DOMException('another call in this decision failed', 'AbortError'));
329
+ return;
330
+ }
331
+ }
332
+ };
333
+ try {
334
+ await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length) }, worker));
335
+ } finally {
336
+ signal?.removeEventListener('abort', onAbort);
337
+ }
338
+ if (failed) throw failure;
339
+ signal?.throwIfAborted();
340
+ return results;
341
+ }
342
+
343
+ function boundedCount(value: number | undefined, fallback: number): number {
344
+ if (!Number.isInteger(value) || (value as number) < 1) return fallback;
345
+ return Math.min(value as number, MAX_SAMPLES);
346
+ }
347
+
348
+ function parseSample(schema: DecisionSchema, content: string, logicalName: string): unknown {
349
+ try {
350
+ return parseDecisionSample(schema, content);
351
+ } catch (err) {
352
+ throw new GenerativeDecisionError(
353
+ `generative model '${logicalName}' produced a sample outside the decision schema: ${(err as Error).message}`
354
+ );
355
+ }
356
+ }
357
+
358
+ /** Normalize the backend's log-likelihoods over the leaf's values; the shape is checked because no backend's arithmetic is trusted. */
359
+ function softmax(values: readonly unknown[], logLikelihoods: unknown, logicalName: string): DecisionOutcome[] {
360
+ if (
361
+ !Array.isArray(logLikelihoods) ||
362
+ logLikelihoods.length !== values.length ||
363
+ !logLikelihoods.every((x) => typeof x === 'number' && Number.isFinite(x))
364
+ )
365
+ throw new GenerativeDecisionError(
366
+ `generative model '${logicalName}' did not return one finite log-likelihood per allowed value (${values.length})`
367
+ );
368
+ const scores = logLikelihoods as number[];
369
+ const max = Math.max(...scores);
370
+ const weights = scores.map((x) => Math.exp(x - max));
371
+ const total = weights.reduce((sum, w) => sum + w, 0);
372
+ return values.map((value, i) => ({ value, probability: weights[i] / total }));
373
+ }
374
+
375
+ function buildInput(
376
+ state: DecideInput,
377
+ schema: DecisionSchema,
378
+ instructions: string | undefined,
379
+ system: string,
380
+ field: string | undefined
381
+ ): GenerateInput {
382
+ const lines: string[] = [];
383
+ if (instructions) lines.push(instructions, '');
384
+ if (schema.description) lines.push(schema.description, '');
385
+ if (!isObjectSchema(schema)) {
386
+ lines.push(`Decide "value": ${describeLeaf(schema)}`);
387
+ } else if (field !== undefined) {
388
+ lines.push(`Decide "${field}": ${describeLeaf(schema.properties[field])}`);
389
+ } else {
390
+ lines.push('Decide each field:');
391
+ for (const [name, leaf] of Object.entries(schema.properties)) lines.push(`- ${name}: ${describeLeaf(leaf)}`);
392
+ }
393
+ lines.push('', 'Input:', stateToText(state));
394
+ return { system, messages: [{ role: 'user', content: lines.join('\n') }] };
395
+ }
396
+
397
+ function describeLeaf(leaf: DecisionLeaf): string {
398
+ const range =
399
+ !('enum' in leaf) && leaf.type === 'integer'
400
+ ? `an integer from ${leaf.minimum} to ${leaf.maximum}`
401
+ : `one of ${allowedValues(leaf)
402
+ .map((value) => JSON.stringify(value))
403
+ .join(', ')}`;
404
+ return leaf.description ? `${leaf.description} (${range})` : range;
405
+ }
406
+
407
+ function tally(schema: DecisionSchema, votes: unknown[]): DecisionOutput<unknown> {
408
+ if (isObjectSchema(schema)) {
409
+ const fields: Record<string, DecisionOutput<unknown>> = {};
410
+ for (const [name, leaf] of Object.entries(schema.properties))
411
+ fields[name] = tallyLeaf(
412
+ leaf,
413
+ votes.map((vote) => (vote as Record<string, unknown>)[name])
414
+ );
415
+ return { fields };
416
+ }
417
+ return tallyLeaf(schema, votes);
418
+ }
419
+
420
+ function tallyLeaf(leaf: DecisionLeaf, votes: unknown[]): DecisionOutput<unknown> {
421
+ if (leaf.noMatch !== true) return { distribution: frequencies(leaf, votes) };
422
+ const values: unknown[] = new Array(votes.length);
423
+ let none = 0;
424
+ for (let i = 0; i < votes.length; i++) {
425
+ const pair = votes[i] as SamplePair;
426
+ values[i] = pair.value;
427
+ if (pair.noMatch) none++;
428
+ }
429
+ return { distribution: frequencies(leaf, values), noMatch: none / votes.length };
430
+ }
431
+
432
+ const NO_MATCH = Symbol('noMatch');
433
+
434
+ interface LeafScore {
435
+ distribution: DecisionOutcome[];
436
+ noMatch?: number;
437
+ }
438
+
439
+ function voteNoMatchLine(schema: DecisionSchema): string {
440
+ const rule =
441
+ 'give the closest allowed value as "value", and set "noMatch" to true only when none of the allowed values truly fits the input.';
442
+ if (!isObjectSchema(schema)) return `Answer as { "value", "noMatch" }: ${rule}`;
443
+ const names = Object.entries(schema.properties)
444
+ .filter(([, leaf]) => leaf.noMatch === true)
445
+ .map(([name]) => JSON.stringify(name));
446
+ return `Answer ${names.join(', ')} each as { "value", "noMatch" }: ${rule}`;
447
+ }
448
+
449
+ function noMatchChoice(choices: readonly string[]): string {
450
+ let text = NO_MATCH_CHOICE;
451
+ while (choices.includes(text)) text = `(${text})`;
452
+ return text;
453
+ }
454
+
455
+ function frequencies(leaf: DecisionLeaf, votes: unknown[]): DecisionOutcome[] {
456
+ const counts = new Map<unknown, number>();
457
+ for (const vote of votes) counts.set(vote, (counts.get(vote) ?? 0) + 1);
458
+ return allowedValues(leaf).map((value) => ({ value, probability: (counts.get(value) ?? 0) / votes.length }));
459
+ }