failproofai 1.0.7-beta.2 → 1.0.7

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 (252) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +7 -8
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js +5 -7
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +6 -6
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +11 -13
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js +7 -8
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +7 -7
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js +6 -7
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -41
  63. package/.next/standalone/.next/server/app/settings/page.js +9 -12
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/{[externals]__1j-zsg5._.js → [externals]__1_bftcl._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__19_pzeq._.js → [externals]__1msfs-h._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1bf34x4._.js → [root-of-the-server]__1_r2rbg._.js} +7 -5
  71. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  72. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1c3k-8x._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  76. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -4
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +4 -0
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__0bd3mje._.js} +2 -2
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +5 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__0cxe_2_._.js} +3 -3
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +22 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +5 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
  98. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/{_1w_5l7t._.js → _0h_douw._.js} +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-i_gzc._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _166t73i._.js} +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/{_1q46vxx._.js → _1_qswah._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifp._.js → _1es2j7i._.js} +5 -5
  104. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  107. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
  113. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  116. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  117. package/.next/standalone/.next/server/pages/404.html +1 -1
  118. package/.next/standalone/.next/server/pages/500.html +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/server-reference-manifest.json +23 -56
  121. package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +1 -0
  122. package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 0__8a7m868fvf.js} +1 -1
  123. package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 0o6qlkgubtoex.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 13i7-9is-vhys.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +6 -0
  126. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
  127. package/.next/standalone/.next/static/chunks/{2_pltstd8-xgs.js → 2klitrtzpaoe0.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 2mdh397ghgnvv.js} +1 -1
  129. package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +2 -0
  130. package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +1 -0
  131. package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +69 -0
  132. package/.next/standalone/.next/static/chunks/{1qd741hzlmjbo.js → 43ufqrz8qo3h-.js} +1 -1
  133. package/.next/standalone/SECURITY.md +53 -0
  134. package/.next/standalone/app/actions/pack-actions.ts +0 -12
  135. package/.next/standalone/app/policies/hooks-client.tsx +0 -9
  136. package/.next/standalone/app/settings/page.tsx +1 -20
  137. package/.next/standalone/app/settings/settings-client.tsx +1 -27
  138. package/.next/standalone/app/settings/settings.css +0 -79
  139. package/.next/standalone/package.json +10 -10
  140. package/.next/standalone/sdk/typescript/CHANGELOG.md +15 -1
  141. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +10 -30
  142. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +3 -0
  143. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -16
  144. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +3 -0
  145. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +13 -132
  146. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +4 -0
  147. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -142
  148. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +4 -0
  149. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +1396 -1016
  150. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +9 -0
  151. package/.next/standalone/server.js +1 -1
  152. package/README.md +2 -2
  153. package/bin/failproofai.mjs +2 -115
  154. package/dist/cli.mjs +6543 -13872
  155. package/dist/index.js +1 -19
  156. package/dist/worker.mjs +2022 -8055
  157. package/package.json +10 -10
  158. package/pi-extension/index.ts +0 -11
  159. package/scripts/build-policy-pack.mjs +2 -53
  160. package/src/audit/features.ts +2 -3
  161. package/src/hooks/builtin-policies.ts +8 -177
  162. package/src/hooks/cloud-enrollment-cli.ts +1 -1
  163. package/src/hooks/cloud-managed-policies.ts +0 -22
  164. package/src/hooks/custom-hooks-loader.ts +7 -45
  165. package/src/hooks/custom-hooks-registry.ts +1 -45
  166. package/src/hooks/first-run-gate.ts +0 -5
  167. package/src/hooks/fp-home.ts +0 -23
  168. package/src/hooks/handler.ts +6 -265
  169. package/src/hooks/hook-activity-store.ts +1 -105
  170. package/src/hooks/hook-telemetry.ts +0 -41
  171. package/src/hooks/loader-utils.ts +0 -6
  172. package/src/hooks/manager.ts +1 -1
  173. package/src/hooks/pack-cli.ts +19 -412
  174. package/src/hooks/pack-manifest.ts +7 -479
  175. package/src/hooks/pack-store.ts +11 -157
  176. package/src/hooks/policy-catalog.ts +0 -204
  177. package/src/hooks/policy-evaluator.ts +796 -940
  178. package/src/hooks/policy-registry.ts +0 -25
  179. package/src/hooks/policy-types.ts +0 -126
  180. package/src/hooks/types.ts +1 -1
  181. package/src/hooks/worker-server.ts +26 -119
  182. package/src/index.ts +0 -6
  183. package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
  184. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
  185. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +0 -3
  186. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +0 -4
  187. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  188. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +0 -3
  189. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +0 -22
  190. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  191. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +0 -3
  192. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +0 -5
  193. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  194. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
  195. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +0 -3
  196. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  197. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  198. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +0 -3
  199. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +0 -3
  200. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +0 -4
  201. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  202. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +0 -3
  203. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +0 -3
  204. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +0 -3
  206. package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
  207. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +0 -3
  208. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +0 -3
  209. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +0 -151
  210. package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
  211. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
  212. package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
  213. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
  214. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
  215. package/.next/standalone/.next/static/chunks/043j99m8ykg__.css +0 -2
  216. package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
  217. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +0 -1
  218. package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
  219. package/.next/standalone/.next/static/chunks/3otmypm6j_xfo.js +0 -6
  220. package/.next/standalone/.next/static/chunks/3yxro_r2_o9ad.js +0 -69
  221. package/.next/standalone/PROBE-FOLLOWUP.md +0 -186
  222. package/.next/standalone/app/actions/get-jev-config.ts +0 -424
  223. package/.next/standalone/app/actions/update-jev-config.ts +0 -420
  224. package/.next/standalone/app/components/jev-notices.tsx +0 -96
  225. package/.next/standalone/app/settings/jev-panel.tsx +0 -473
  226. package/src/hooks/effective-reviewers.ts +0 -79
  227. package/src/hooks/jev-activity.ts +0 -385
  228. package/src/hooks/jev-cli.ts +0 -1249
  229. package/src/hooks/policy-authority.ts +0 -333
  230. package/src/hooks/policy-reviewability.ts +0 -229
  231. package/src/hooks/semantic/combine.ts +0 -541
  232. package/src/hooks/semantic/compile.ts +0 -176
  233. package/src/hooks/semantic/decide.ts +0 -392
  234. package/src/hooks/semantic/envelope.ts +0 -1296
  235. package/src/hooks/semantic/evaluator.ts +0 -547
  236. package/src/hooks/semantic/facts.ts +0 -292
  237. package/src/hooks/semantic/intent.ts +0 -1190
  238. package/src/hooks/semantic/jev-client.ts +0 -643
  239. package/src/hooks/semantic/jev-config.ts +0 -594
  240. package/src/hooks/semantic/jev-review.ts +0 -374
  241. package/src/hooks/semantic/jev-stats.ts +0 -289
  242. package/src/hooks/semantic/jev-throttle.ts +0 -421
  243. package/src/hooks/semantic/pack-policies.ts +0 -251
  244. package/src/hooks/semantic/policies.ts +0 -596
  245. package/src/hooks/semantic/precondition-names.ts +0 -60
  246. package/src/hooks/semantic/preconditions.ts +0 -58
  247. package/src/hooks/semantic/redact.ts +0 -2910
  248. package/src/hooks/semantic/types.ts +0 -145
  249. package/src/hooks/semver-precedence.ts +0 -128
  250. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_buildManifest.js +0 -0
  251. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_clientMiddlewareManifest.js +0 -0
  252. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_ssgManifest.js +0 -0
@@ -1,643 +0,0 @@
1
- /**
2
- * The client for Jev, and the provider layer that turns a customer's own
3
- * config (BYOK, `jev-config.ts`) into a transport.
4
- *
5
- * # Five routes, two wire shapes
6
- *
7
- * | Provider | Endpoint | Default model |
8
- * |------------|-------------------------------------------------------|----------------------|
9
- * | typesafe | `https://api.typesafe.ai/v1/systemone` | `jev-1.13.0` |
10
- * | openrouter | `https://openrouter.ai/api/v1/systemone` | `typesafe/jev-1.13` |
11
- * | vercel | `https://ai-gateway.vercel.sh/typesafe/v1/systemone` | `typesafe-ai/jev` |
12
- * | cloudflare | `https://api.cloudflare.com/client/v4/accounts/<id>/ai/run` | `typesafe/jev` |
13
- * | custom | `<baseUrl>/systemone` | `jev-1.13.0` |
14
- *
15
- * TypeSafe, OpenRouter, Vercel and a custom URL all take TypeSafe's native body
16
- * `{model, state, questions}` and answer `{model, answers, usage}`, so one
17
- * transport (`nativeTransport`) serves all four: base URL, `Bearer` key and the
18
- * right model id. OpenRouter additionally gets
19
- * `provider: {zdr: true, data_collection: "deny", allow_fallbacks: false}` so a
20
- * tool call is only ever routed to a zero-data-retention endpoint and never
21
- * silently to another model. Cloudflare Workers AI wraps the request as
22
- * `{model: "typesafe/jev", input: {state, questions}}` and nests the answer
23
- * under a job layer (`result.result`), so it keeps its own transport.
24
- *
25
- * Auth everywhere is `Authorization: Bearer <key>`. The key never appears in an
26
- * error message: provider error text is passed through with the key scrubbed.
27
- *
28
- * # Which Jev answered
29
- *
30
- * The decision thresholds were calibrated against Jev 1.13, so `readAnswers`
31
- * accepts an answer only from that family: `jev-1.13.x`, OpenRouter's
32
- * `typesafe/jev-1.13-<date>` snapshot, or — where the provider reports no
33
- * version at all (Vercel's `typesafe-ai/jev` or no model, Cloudflare's
34
- * `typesafe/jev` or no model, a custom proxy echoing back the unversioned name
35
- * the customer configured for it) — an answer the TRANSPORT marked
36
- * `modelUnverified`, which the evaluator records as `modelVerified: false`. A
37
- * reported version of another major.minor is a `model-mismatch`, and the caller
38
- * falls back to regex; so is a custom endpoint that reports no model at all. The flag is set only by the transports in this file from
39
- * what the provider reported; a `modelUnverified` field in a response body is
40
- * never copied through.
41
- *
42
- * # Errors
43
- *
44
- * Every failure is a `JevError`, and the caller falls back to regex:
45
- * `timeout`, `network`, `http-<status>` (429 and every 5xx included, and every
46
- * 3xx: a redirect is never followed, so the answer only ever comes from the
47
- * configured origin),
48
- * `out-of-credits` and `provider-refused` (the two meanings of HTTP 402, or of
49
- * a 402 inside a 200 body — see `paymentRequiredCode`), `upstream-error`,
50
- * `cloudflare-error`, `cloudflare-incomplete`, `malformed`, `model-mismatch`,
51
- * `config`.
52
- *
53
- * # When the provider refuses the call
54
- *
55
- * A refusal is not spread evenly over the traffic: the provider is likeliest to
56
- * decline exactly the calls that matter most — the curl that reads like
57
- * exfiltration, the file listing carrying `rm -rf /` and `dd if=/dev/zero`.
58
- * Both the judged call and our own question text ride in the request, so either
59
- * can trip it; one of our own shipped examples did, live (see
60
- * `download_and_run` in `policies.ts`).
61
- *
62
- * The consequence is a security property, not a nicety. A refusal degrades the
63
- * call to the regex policies — which is the correct fallback: every hard policy
64
- * still denies, and no reviewable deny is ever cleared by an answer nobody got
65
- * — but the degrade has to be VISIBLE, because it lands on the worst call of
66
- * the day rather than a random one. It is visible as `jev-fallback` /
67
- * `provider-refused` on the activity row and in `failproofai jev status`.
68
- * Filing it as `out-of-credits` was the opposite of visible: it sent the
69
- * operator to their billing page while that call went through on regex alone.
70
- *
71
- * # Not an opt-in: `resolveJevProvider`
72
- *
73
- * The research harness found credentials in `~/.config/typesafe/` (or
74
- * `FAILPROOFAI_JEV_CONFIG_DIR`, or `TYPESAFE_API_KEY`). That lookup is kept for
75
- * the harness only. The product's ONLY opt-in is the global
76
- * `~/.failproofai/jev.json`, read by `loadJevConfig()`; the hook path always
77
- * builds its transport with `transportForConfig(loadJevConfig())` and never
78
- * falls back to `resolveJevProvider`.
79
- *
80
- * Deliberately raw `fetch` and no SDK: `policy-evaluator.ts` records a single
81
- * import being weighed in bytes on the hook path.
82
- */
83
- import { readFileSync } from "node:fs";
84
- import { homedir } from "node:os";
85
- import { resolve } from "node:path";
86
- // A value, not a literal: the code has to be one the activity store's closed
87
- // list names, and importing the constant makes a rename there a compile error
88
- // here instead of a row quietly stored as `other`. `jev-activity.ts` is pure
89
- // (no node imports, no semantic modules), as `combine.ts` already relies on.
90
- import { JEV_REASON_PROVIDER_REFUSED } from "../jev-activity";
91
- import {
92
- CLOUDFLARE_ACCOUNT_ID_RE,
93
- isCalibratedJevModel,
94
- jevModelVersion,
95
- validateJevConfig,
96
- type JevConfig,
97
- type JevProviderKind,
98
- } from "./jev-config";
99
- import type { JevRequest, JevResponse } from "./types";
100
-
101
- export const JEV_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
102
- /** Cloudflare's name for Jev. Unversioned — see `unwrapCloudflare`. */
103
- export const CLOUDFLARE_JEV_MODEL = "typesafe/jev";
104
- /** Vercel AI Gateway's name for Jev. An alias: Vercel reports no version. */
105
- export const VERCEL_JEV_MODEL = "typesafe-ai/jev";
106
- export const cloudflareRunEndpoint = (accountId: string, apiBase = JEV_PROVIDER_DEFAULTS.cloudflare.baseUrl): string =>
107
- `${apiBase.replace(/\/+$/, "")}/accounts/${accountId}/ai/run`;
108
-
109
- /** $0.042 per million input tokens; output tokens are free. */
110
- export const JEV_USD_PER_INPUT_TOKEN = 0.042 / 1_000_000;
111
-
112
- /**
113
- * Where each provider lives and which model id it knows Jev by. `baseUrl` is the
114
- * API base: native providers POST to `<baseUrl>/systemone`, Cloudflare to
115
- * `<baseUrl>/accounts/<id>/ai/run`. A config's `baseUrl` replaces it.
116
- */
117
- export const JEV_PROVIDER_DEFAULTS = {
118
- typesafe: { baseUrl: "https://api.typesafe.ai/v1", model: "jev-1.13.0" },
119
- openrouter: { baseUrl: "https://openrouter.ai/api/v1", model: "typesafe/jev-1.13" },
120
- vercel: { baseUrl: "https://ai-gateway.vercel.sh/typesafe/v1", model: VERCEL_JEV_MODEL },
121
- cloudflare: { baseUrl: "https://api.cloudflare.com/client/v4", model: CLOUDFLARE_JEV_MODEL },
122
- custom: { baseUrl: null, model: "jev-1.13.0" },
123
- } as const satisfies Record<JevProviderKind, { baseUrl: string | null; model: string }>;
124
-
125
- /**
126
- * OpenRouter provider routing: zero-data-retention endpoints only, no training
127
- * on the data, and no fallback to a different provider or model when the pinned
128
- * one is down — a fallback answer is exactly the "different model" the version
129
- * check exists to refuse.
130
- */
131
- export const OPENROUTER_PROVIDER_PREFS = { zdr: true, data_collection: "deny", allow_fallbacks: false } as const;
132
-
133
- export type JevTransport = (request: JevRequest, signal: AbortSignal) => Promise<JevResponse>;
134
-
135
- export class JevError extends Error {
136
- /**
137
- * Short, stable cause used in logs and as the fallback reason: timeout,
138
- * network, http-<status>, out-of-credits, provider-refused, upstream-error,
139
- * cloudflare-error, cloudflare-incomplete, malformed, model-mismatch, config.
140
- */
141
- readonly code: string;
142
- constructor(code: string, message: string) {
143
- super(message);
144
- this.name = "JevError";
145
- this.code = code;
146
- }
147
- }
148
-
149
- // ── Research-harness credentials (NOT an opt-in) ─────────────────────────────
150
-
151
- export function jevConfigDir(): string {
152
- return process.env.FAILPROOFAI_JEV_CONFIG_DIR || resolve(homedir(), ".config", "typesafe");
153
- }
154
-
155
- function readTrimmed(file: string): string | null {
156
- try {
157
- const v = readFileSync(file, "utf8").trim();
158
- return v.length > 0 ? v : null;
159
- } catch {
160
- return null;
161
- }
162
- }
163
-
164
- export type JevProvider =
165
- | { kind: "typesafe"; apiKey: string }
166
- | { kind: "cloudflare"; token: string; accountId: string };
167
-
168
- /**
169
- * The research harness's credential lookup: a TypeSafe key (env or
170
- * `api_key`), else a Cloudflare token plus `cloudflare.json`. A Cloudflare
171
- * account id that is not 32 hex characters is treated as not configured — it
172
- * is interpolated into a URL path.
173
- *
174
- * NOT an opt-in and never consulted on the hook path: the product turns Jev on
175
- * only through `~/.failproofai/jev.json` (`loadJevConfig`), and the handler
176
- * always passes an explicit transport from `transportForConfig`.
177
- */
178
- export function resolveJevProvider(): JevProvider | null {
179
- const dir = jevConfigDir();
180
- const apiKey = process.env.TYPESAFE_API_KEY?.trim() || readTrimmed(resolve(dir, "api_key"));
181
- if (apiKey) return { kind: "typesafe", apiKey };
182
- const token = readTrimmed(resolve(dir, "cloudflare_token"));
183
- if (!token) return null;
184
- let accountId: unknown;
185
- try {
186
- accountId = (JSON.parse(readFileSync(resolve(dir, "cloudflare.json"), "utf8")) as { accountId?: unknown }).accountId;
187
- } catch {
188
- return null;
189
- }
190
- if (typeof accountId !== "string" || !CLOUDFLARE_ACCOUNT_ID_RE.test(accountId)) return null;
191
- return { kind: "cloudflare", token, accountId };
192
- }
193
-
194
- /** Research harness only; see `resolveJevProvider`. */
195
- export function transportFor(provider: JevProvider): JevTransport {
196
- return provider.kind === "typesafe" ? httpTransport(provider.apiKey) : cloudflareTransport(provider.token, provider.accountId);
197
- }
198
-
199
- // ── HTTP ─────────────────────────────────────────────────────────────────────
200
-
201
- const MAX_ERROR_DETAIL = 300;
202
-
203
- /** The provider's own words about a failure, from whichever envelope it uses, with the key scrubbed. */
204
- function errorDetail(body: unknown, secret: string): string {
205
- const b = body as {
206
- errors?: Array<{ message?: unknown }>;
207
- error?: unknown;
208
- message?: unknown;
209
- detail?: unknown;
210
- } | null;
211
- let detail = "";
212
- if (b && typeof b === "object") {
213
- if (Array.isArray(b.errors)) {
214
- detail = b.errors.map((e) => (typeof e?.message === "string" ? e.message : "")).filter(Boolean).join("; ");
215
- }
216
- if (!detail && b.error && typeof b.error === "object") {
217
- const m = (b.error as { message?: unknown }).message;
218
- if (typeof m === "string") detail = m;
219
- }
220
- if (!detail && typeof b.error === "string") detail = b.error;
221
- if (!detail && typeof b.message === "string") detail = b.message;
222
- if (!detail && typeof b.detail === "string") detail = b.detail;
223
- }
224
- return scrubSecret(detail, secret).slice(0, MAX_ERROR_DETAIL);
225
- }
226
-
227
- /**
228
- * Replace every occurrence of the key. A provider echoing a credential must not
229
- * put it in a log line. Exported so a caller that prints an error (`jev test`)
230
- * can scrub it again, whatever path the text took to get there.
231
- */
232
- export function scrubSecret(text: string, secret: string): string {
233
- return secret.length >= 4 ? text.split(secret).join("[key]") : text;
234
- }
235
-
236
- /**
237
- * Cloudflare's wording for a request it would not run the model on, seen live
238
- * (2026-09-21 to 24) as HTTP 402 with
239
- * `{"errors":[{"message":"Model execution failed (Payment error)","code":2021}]}`.
240
- *
241
- * Matched on the wording, not on the numeric code: 2021 is the only value
242
- * observed and nothing here knows which other codes Cloudflare files under the
243
- * same meaning, so testing the code would be a guess where the text is an
244
- * observation.
245
- */
246
- const MODEL_EXECUTION_402_RE = /model execution failed/i;
247
-
248
- /**
249
- * Which of the two quite different things an HTTP 402 means here, from the
250
- * provider's own words (already scrubbed; `""` when it sent none).
251
- *
252
- * The two are: the account really has no credits left, and the provider
253
- * DECLINED TO PROCESS THE REQUEST. The second is not a theory — on the
254
- * Cloudflare route it is reproducible: one shipped question's example text made
255
- * every call that selected that policy fail with 402, deterministically, on any
256
- * state, at well under a request a second, with no quota, size or rate
257
- * condition in play, and the failures stopped when a few characters of the
258
- * question changed (`download_and_run` in `policies.ts`). Either half of the
259
- * request can trip it — our questions or the judged call itself, which is why
260
- * an agent's own command can cause it.
261
- *
262
- * The response is all the evidence there is, and it does not settle the two in
263
- * general. "Model execution failed (Payment error)" is Cloudflare reporting
264
- * that the model RUN was refused for a payment-category reason: what we have
265
- * watched it mean is a content refusal, but a partner-side billing failure
266
- * would arrive wearing the same wrapper, and nothing in the body tells those
267
- * apart. So this shape is `provider-refused` — a name that says who refused and
268
- * that no answer arrived, and accuses the operator's balance under neither
269
- * reading. Any other 402 (a body that names credits, a non-JSON body, no body)
270
- * stays `out-of-credits`: the status's own meaning, and the only thing left to
271
- * call it.
272
- *
273
- * The gap that remains: a route refusing content in different words is still
274
- * filed as `out-of-credits`. A fallback histogram filling with `out-of-credits`
275
- * while the account demonstrably has money is the sign to sweep the questions
276
- * again, the way `policies.ts` describes.
277
- */
278
- function paymentRequiredCode(detail: string): string {
279
- return MODEL_EXECUTION_402_RE.test(detail) ? JEV_REASON_PROVIDER_REFUSED : "out-of-credits";
280
- }
281
-
282
- /** A redirect, including the opaque form a browser-style fetch returns for `redirect: "manual"` (status 0). */
283
- function isRedirect(res: Response): boolean {
284
- return res.type === "opaqueredirect" || (res.status >= 300 && res.status < 400);
285
- }
286
-
287
- async function postJson(url: string, bearer: string, body: unknown, signal: AbortSignal): Promise<unknown> {
288
- let res: Response;
289
- try {
290
- res = await fetch(url, {
291
- method: "POST",
292
- headers: { Authorization: `Bearer ${bearer}`, "Content-Type": "application/json" },
293
- body: JSON.stringify(body),
294
- signal,
295
- // Never followed. The configured URL is the one `validateBaseUrl` checked
296
- // (https, or loopback http in shadow mode only); a redirect would hand the
297
- // answer — the thing that can clear a deny — to an origin nobody checked,
298
- // plain http included. No provider redirects this POST.
299
- redirect: "manual",
300
- });
301
- } catch (err) {
302
- if (signal.aborted) throw new JevError("timeout", "Jev did not answer in time");
303
- throw new JevError("network", scrubSecret(err instanceof Error ? err.message : String(err), bearer));
304
- }
305
- if (isRedirect(res)) {
306
- try {
307
- void res.body?.cancel().catch(() => {});
308
- } catch {
309
- // The body is irrelevant; freeing it is best effort.
310
- }
311
- const status = res.status >= 300 && res.status < 400 ? String(res.status) : "3xx";
312
- throw new JevError(`http-${status}`, `HTTP ${status}: the endpoint answered with a redirect, which is never followed`);
313
- }
314
- let parsed: unknown;
315
- try {
316
- parsed = await res.json();
317
- } catch {
318
- if (signal.aborted) throw new JevError("timeout", "Jev did not answer in time");
319
- // No provider words to read, so nothing distinguishes a refusal from an
320
- // empty account: the status's own meaning stands (see `paymentRequiredCode`).
321
- if (res.status === 402) throw new JevError("out-of-credits", "HTTP 402: the account is out of credits");
322
- if (!res.ok) throw new JevError(`http-${res.status}`, `HTTP ${res.status}`);
323
- throw new JevError("malformed", "response body is not JSON");
324
- }
325
- if (res.status === 402) {
326
- // A refusal is only ever recognised FROM the provider's words, so a 402
327
- // with none is `out-of-credits` and this message fits it. Anything the
328
- // provider did say is the message, refusal or not.
329
- const detail = errorDetail(parsed, bearer);
330
- throw new JevError(paymentRequiredCode(detail), detail || "HTTP 402: the account is out of credits");
331
- }
332
- if (!res.ok) {
333
- throw new JevError(`http-${res.status}`, errorDetail(parsed, bearer) || `HTTP ${res.status}`);
334
- }
335
- return parsed;
336
- }
337
-
338
- /** A model id that names no version and is one of the names this route is known by. */
339
- function isAliasFor(reported: string, aliases: readonly string[]): boolean {
340
- return jevModelVersion(reported) === null && aliases.includes(reported);
341
- }
342
-
343
- // ── TypeSafe-native transport (typesafe, openrouter, vercel, custom) ─────────
344
-
345
- export interface NativeTransportOptions {
346
- /** The full endpoint URL, e.g. `https://api.typesafe.ai/v1/systemone`. */
347
- url: string;
348
- apiKey: string;
349
- /** Sent as the body's `model`, replacing the request's. Omitted: the request's own. */
350
- model?: string;
351
- /** Extra top-level body fields — OpenRouter's `provider` routing. */
352
- extraBody?: Record<string, unknown>;
353
- /** Unversioned names this route reports for Jev; answering with one marks the response `modelUnverified`. */
354
- aliases?: readonly string[];
355
- /** Whether an answer with no `model` at all is accepted (as unverified) rather than refused. */
356
- allowUnreported?: boolean;
357
- }
358
-
359
- /**
360
- * A provider's body → a Jev response, built field by field so nothing but
361
- * `model`, `answers` and `usage` crosses over — in particular never a
362
- * `modelUnverified` the server chose to send.
363
- */
364
- function normalizeNative(body: unknown, sentModel: string, opts: NativeTransportOptions): JevResponse {
365
- if (!body || typeof body !== "object" || Array.isArray(body)) throw new JevError("malformed", "Jev returned no object");
366
- const b = body as { model?: unknown; answers?: unknown; usage?: unknown; error?: unknown };
367
- if (typeof b.answers !== "object" || b.answers === null) {
368
- // Gateways sometimes report an upstream failure inside a 200.
369
- if (b.error !== undefined) {
370
- const code = typeof b.error === "object" && b.error !== null ? (b.error as { code?: unknown }).code : undefined;
371
- const detail = errorDetail(body, opts.apiKey) || "the provider reported an error";
372
- // Same two meanings, one layer in; same rule (see `paymentRequiredCode`).
373
- if (code === 402 || code === "402") throw new JevError(paymentRequiredCode(detail), detail);
374
- if (typeof code === "number" && (code === 429 || code >= 500)) throw new JevError(`http-${code}`, detail);
375
- throw new JevError("upstream-error", detail);
376
- }
377
- throw new JevError("malformed", "Jev response has no answers object");
378
- }
379
- const usage = b.usage && typeof b.usage === "object" ? (b.usage as JevResponse["usage"]) : undefined;
380
- // The reported id reaches a `model-mismatch` message, so a server echoing the key there must not carry it through.
381
- const reported = typeof b.model === "string" && b.model.length > 0 ? scrubSecret(b.model, opts.apiKey) : null;
382
- let model: string;
383
- let unverified = false;
384
- if (reported === null) {
385
- // Nothing to check. Accepted only where the route is known not to report.
386
- model = opts.allowUnreported ? sentModel : "";
387
- unverified = opts.allowUnreported === true;
388
- } else {
389
- model = reported;
390
- unverified = isAliasFor(reported, opts.aliases ?? []);
391
- }
392
- return {
393
- model,
394
- answers: b.answers as JevResponse["answers"],
395
- ...(usage ? { usage } : {}),
396
- ...(unverified ? { modelUnverified: true } : {}),
397
- };
398
- }
399
-
400
- export function nativeTransport(opts: NativeTransportOptions): JevTransport {
401
- return async (request, signal) => {
402
- const model = opts.model ?? request.model;
403
- const body = { ...request, model, ...(opts.extraBody ?? {}) };
404
- return normalizeNative(await postJson(opts.url, opts.apiKey, body, signal), model, opts);
405
- };
406
- }
407
-
408
- /** TypeSafe direct at its fixed endpoint, sending the request's own model. */
409
- export function httpTransport(apiKey: string): JevTransport {
410
- return nativeTransport({ url: JEV_ENDPOINT, apiKey });
411
- }
412
-
413
- // ── Cloudflare Workers AI ────────────────────────────────────────────────────
414
-
415
- export interface CloudflareTransportOptions {
416
- /** Cloudflare's model id. Default `typesafe/jev`. */
417
- model?: string;
418
- /** Replaces `https://api.cloudflare.com/client/v4`; a URL already ending in `/ai/run` is used as is. */
419
- baseUrl?: string;
420
- }
421
-
422
- export function cloudflareTransport(token: string, accountId: string, opts: CloudflareTransportOptions = {}): JevTransport {
423
- if (!CLOUDFLARE_ACCOUNT_ID_RE.test(accountId)) throw new JevError("config", "Cloudflare account id must be 32 hex characters");
424
- const model = opts.model ?? CLOUDFLARE_JEV_MODEL;
425
- const endpoint = cloudflareEndpoint(accountId, opts.baseUrl);
426
- return async (request, signal) => {
427
- const body = await postJson(endpoint, token, { model, input: { state: request.state, questions: request.questions } }, signal);
428
- return unwrapCloudflare(body, request, model, token);
429
- };
430
- }
431
-
432
- function cloudflareEndpoint(accountId: string, baseUrl?: string): string {
433
- if (!baseUrl) return cloudflareRunEndpoint(accountId);
434
- const url = new URL(baseUrl);
435
- if (/\/ai\/run$/.test(url.pathname)) return url.toString();
436
- url.pathname = `${url.pathname.replace(/\/+$/, "")}/accounts/${accountId}/ai/run`;
437
- return url.toString();
438
- }
439
-
440
- /**
441
- * Cloudflare's `{success, errors, result}` envelope → a Jev response.
442
- *
443
- * Cloudflare addresses Jev as `typesafe/jev`, without a version. When the
444
- * result says which Jev answered, `readAnswers` enforces the family exactly as
445
- * it does for TypeSafe. When it does not — no `model`, or only the alias echoed
446
- * back — the version cannot be checked from here: the response carries the
447
- * request's model so the answers are usable, and `modelUnverified` puts that
448
- * gap in the verdict log instead of hiding it.
449
- *
450
- * `secret` is the token the request was sent with. Every piece of provider
451
- * text that can reach an error message (`errors[].message`, a job state, a
452
- * reported model id) is scrubbed of it: a 200 `{success: false}` is not
453
- * scrubbed by `postJson`, which sees only the status.
454
- */
455
- export function unwrapCloudflare(
456
- body: unknown,
457
- request: JevRequest,
458
- sentModel: string = CLOUDFLARE_JEV_MODEL,
459
- secret = "",
460
- ): JevResponse {
461
- const envelope = body as { success?: unknown; errors?: Array<{ message?: unknown }>; result?: unknown } | null;
462
- if (!envelope || typeof envelope !== "object") throw new JevError("malformed", "Cloudflare returned no object");
463
- if (envelope.success === false) {
464
- const detail = Array.isArray(envelope.errors)
465
- ? envelope.errors.map((e) => (typeof e?.message === "string" ? e.message : "")).filter(Boolean).join("; ")
466
- : "";
467
- // Not the refusal path: a declined request arrives as HTTP 402 and never
468
- // reaches here (see `paymentRequiredCode`). A 200 `{success: false}` is a
469
- // different failure, and `cloudflare-error` blames nobody's billing either.
470
- throw new JevError("cloudflare-error", scrubSecret(detail, secret).slice(0, MAX_ERROR_DETAIL) || "Cloudflare reported failure");
471
- }
472
- let inner: unknown = "result" in envelope ? envelope.result : envelope;
473
- // Observed live (2026-09-21): partner models add a job layer —
474
- // `result: {state: "Completed", result: <TypeSafe's own response>, gatewayMetadata}`.
475
- // Anything but a completed job is not an answer.
476
- const job = inner as { state?: unknown; result?: unknown } | null;
477
- if (job && typeof job === "object" && "state" in job && "result" in job) {
478
- if (job.state !== "Completed") {
479
- throw new JevError("cloudflare-incomplete", `Cloudflare job state ${scrubSecret(String(job.state), secret).slice(0, MAX_ERROR_DETAIL)}`);
480
- }
481
- inner = job.result;
482
- }
483
- const result = inner as Partial<JevResponse> | null;
484
- if (!result || typeof result !== "object" || typeof result.answers !== "object" || result.answers === null) {
485
- throw new JevError("malformed", "Cloudflare result has no answers");
486
- }
487
- // Echoing back the alias is not a version either.
488
- const reported =
489
- typeof result.model === "string" && result.model.length > 0 && !isAliasFor(result.model, [CLOUDFLARE_JEV_MODEL, sentModel])
490
- ? scrubSecret(result.model, secret)
491
- : null;
492
- return {
493
- model: reported ?? request.model,
494
- answers: result.answers,
495
- ...(result.usage ? { usage: result.usage } : {}),
496
- ...(reported === null ? { modelUnverified: true } : {}),
497
- };
498
- }
499
-
500
- // ── Answers ──────────────────────────────────────────────────────────────────
501
-
502
- /**
503
- * Probabilities keyed by question id, or a JevError. Every question must come
504
- * back as a finite number in [0, 1], and the answer must come from the Jev
505
- * family the thresholds were calibrated against (1.13, any patch or snapshot),
506
- * or from a route that reports no version at all — which the transport has
507
- * marked `modelUnverified`. Thresholds mean nothing for another model.
508
- */
509
- export function readAnswers(request: JevRequest, response: JevResponse): Record<string, number> {
510
- if (!response || typeof response !== "object" || typeof response.answers !== "object" || response.answers === null) {
511
- throw new JevError("malformed", "Jev response has no answers object");
512
- }
513
- if (response.modelUnverified !== true && !isCalibratedJevModel(String(response.model))) {
514
- const got = typeof response.model === "string" && response.model !== "" ? response.model.slice(0, 200) : "no model id";
515
- throw new JevError("model-mismatch", `asked for ${request.model}, got ${got}`);
516
- }
517
- const out: Record<string, number> = {};
518
- for (const id of Object.keys(request.questions)) {
519
- const p = response.answers[id]?.noul;
520
- if (typeof p !== "number" || !Number.isFinite(p) || p < 0 || p > 1) {
521
- throw new JevError("malformed", `answer for ${id} is missing or out of range`);
522
- }
523
- out[id] = p;
524
- }
525
- return out;
526
- }
527
-
528
- // ── BYOK (T1 contract) ───────────────────────────────────────────────────────
529
-
530
- export interface JevRoute {
531
- via: JevProviderKind;
532
- /** The URL requests are POSTed to. May carry a query string; show it with `displayEndpoint`. */
533
- endpoint: string;
534
- /** The model id sent to the provider. */
535
- model: string;
536
- modelIsDefault: boolean;
537
- }
538
-
539
- function nativeEndpoint(baseUrl: string): string {
540
- const url = new URL(baseUrl);
541
- if (!/\/systemone$/.test(url.pathname)) url.pathname = `${url.pathname.replace(/\/+$/, "")}/systemone`;
542
- return url.toString();
543
- }
544
-
545
- function validated(cfg: JevConfig): JevConfig {
546
- const v = validateJevConfig(cfg);
547
- if (!v.ok) throw new JevError("config", v.problem);
548
- return v.value;
549
- }
550
-
551
- /** Where a config sends its requests and which model id it names, without building anything. Throws `JevError("config")`. */
552
- export function jevRoute(input: JevConfig): JevRoute {
553
- const cfg = validated(input);
554
- const defaults = JEV_PROVIDER_DEFAULTS[cfg.provider];
555
- const model = cfg.model ?? defaults.model;
556
- const modelIsDefault = cfg.model === undefined;
557
- if (cfg.provider === "cloudflare") {
558
- return { via: "cloudflare", endpoint: cloudflareEndpoint(cfg.accountId as string, cfg.baseUrl), model, modelIsDefault };
559
- }
560
- const base = cfg.baseUrl ?? defaults.baseUrl;
561
- if (!base) throw new JevError("config", "provider custom needs a baseUrl");
562
- return { via: cfg.provider, endpoint: nativeEndpoint(base), model, modelIsDefault };
563
- }
564
-
565
- /** An endpoint for display: the query string (which may hold anything) replaced by `?…`. */
566
- export function displayEndpoint(endpoint: string): string {
567
- try {
568
- const url = new URL(endpoint);
569
- const shown = `${url.origin}${url.pathname}`;
570
- return url.search ? `${shown}?…` : shown;
571
- } catch {
572
- return "(invalid URL)";
573
- }
574
- }
575
-
576
- /**
577
- * The transport for a customer's own Jev config, which provider it goes
578
- * through, and the model id to put in the request (pass it to the evaluator as
579
- * `model`). The transport sends the configured model regardless, so nothing in
580
- * the environment can change which model a provider is asked for.
581
- *
582
- * Throws `JevError("config")` for a config that would not pass
583
- * `loadJevConfig()`; a caller treats that like any other Jev failure.
584
- */
585
- export function transportForConfig(input: JevConfig): { transport: JevTransport; via: JevProviderKind; model: string } {
586
- const cfg = validated(input);
587
- const route = jevRoute(cfg);
588
- switch (cfg.provider) {
589
- case "cloudflare":
590
- return {
591
- transport: cloudflareTransport(cfg.apiKey, cfg.accountId as string, { model: route.model, baseUrl: cfg.baseUrl }),
592
- via: "cloudflare",
593
- model: route.model,
594
- };
595
- case "typesafe":
596
- return {
597
- transport: nativeTransport({ url: route.endpoint, apiKey: cfg.apiKey, model: route.model }),
598
- via: "typesafe",
599
- model: route.model,
600
- };
601
- case "openrouter":
602
- return {
603
- transport: nativeTransport({
604
- url: route.endpoint,
605
- apiKey: cfg.apiKey,
606
- model: route.model,
607
- extraBody: { provider: { ...OPENROUTER_PROVIDER_PREFS } },
608
- }),
609
- via: "openrouter",
610
- model: route.model,
611
- };
612
- case "vercel":
613
- return {
614
- transport: nativeTransport({
615
- url: route.endpoint,
616
- apiKey: cfg.apiKey,
617
- model: route.model,
618
- aliases: [VERCEL_JEV_MODEL, route.model],
619
- allowUnreported: true,
620
- }),
621
- via: "vercel",
622
- model: route.model,
623
- };
624
- case "custom":
625
- return {
626
- transport: nativeTransport({
627
- url: route.endpoint,
628
- apiKey: cfg.apiKey,
629
- model: route.model,
630
- // A proxy echoing back an UNVERSIONED name the customer configured for
631
- // it (`--model house-jev`) has said nothing about the version, the way
632
- // Vercel's alias has not; that answer is used and marked unverified.
633
- // One reporting a real Jev version is checked. One reporting no model
634
- // at all is refused: unlike Vercel and Cloudflare, nothing is known
635
- // about what sits behind a custom URL, so silence is not accepted.
636
- aliases: [route.model],
637
- allowUnreported: false,
638
- }),
639
- via: "custom",
640
- model: route.model,
641
- };
642
- }
643
- }