okengine 0.18.4 → 0.19.0

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 (512) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/index.mdx +25 -6
  5. package/site/content/docs/ai/llms-txt.mdx +1 -1
  6. package/site/content/docs/ai/meta.json +1 -1
  7. package/site/content/docs/ai/skills.mdx +4 -1
  8. package/site/content/docs/ai/try-it.mdx +58 -0
  9. package/site/content/docs/client/auth.mdx +208 -0
  10. package/site/content/docs/client/calling.mdx +451 -0
  11. package/site/content/docs/client/index.mdx +209 -0
  12. package/site/content/docs/client/live.mdx +234 -0
  13. package/site/content/docs/client/meta.json +5 -0
  14. package/site/content/docs/client/react.mdx +249 -0
  15. package/site/content/docs/elements/ai/agents.mdx +232 -0
  16. package/site/content/docs/elements/ai/index.mdx +341 -0
  17. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  18. package/site/content/docs/elements/ai/meta.json +5 -0
  19. package/site/content/docs/elements/ai/models.mdx +283 -0
  20. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  21. package/site/content/docs/elements/channel/email.mdx +266 -0
  22. package/site/content/docs/elements/channel/index.mdx +344 -0
  23. package/site/content/docs/elements/channel/meta.json +5 -0
  24. package/site/content/docs/elements/channel/push.mdx +218 -0
  25. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  26. package/site/content/docs/elements/channel/sms.mdx +264 -0
  27. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  28. package/site/content/docs/elements/clock/index.mdx +300 -0
  29. package/site/content/docs/elements/clock/meta.json +5 -0
  30. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  31. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  32. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  33. package/site/content/docs/elements/flow/http.mdx +1080 -0
  34. package/site/content/docs/elements/flow/index.mdx +618 -0
  35. package/site/content/docs/elements/flow/meta.json +5 -0
  36. package/site/content/docs/elements/flow/routing.mdx +559 -0
  37. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  38. package/site/content/docs/elements/gate/auth.mdx +371 -0
  39. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  40. package/site/content/docs/elements/gate/index.mdx +418 -0
  41. package/site/content/docs/elements/gate/meta.json +5 -0
  42. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  43. package/site/content/docs/elements/gate/rls.mdx +303 -0
  44. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  45. package/site/content/docs/elements/index.mdx +52 -11
  46. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  47. package/site/content/docs/elements/signal/index.mdx +352 -0
  48. package/site/content/docs/elements/signal/live.mdx +590 -0
  49. package/site/content/docs/elements/signal/meta.json +5 -0
  50. package/site/content/docs/elements/signal/once.mdx +596 -0
  51. package/site/content/docs/elements/store/files.mdx +659 -0
  52. package/site/content/docs/elements/store/index.mdx +310 -0
  53. package/site/content/docs/elements/store/kv.mdx +629 -0
  54. package/site/content/docs/elements/store/meta.json +5 -0
  55. package/site/content/docs/elements/store/search.mdx +946 -0
  56. package/site/content/docs/elements/store/sql.mdx +937 -0
  57. package/site/content/docs/elements/vault/config.mdx +256 -0
  58. package/site/content/docs/elements/vault/index.mdx +356 -0
  59. package/site/content/docs/elements/vault/meta.json +5 -0
  60. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  61. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  62. package/site/content/docs/index.mdx +78 -30
  63. package/site/content/docs/meta.json +11 -8
  64. package/site/content/docs/plugins/cors.mdx +2 -1
  65. package/site/content/docs/plugins/csrf.mdx +4 -1
  66. package/site/content/docs/plugins/headers.mdx +2 -1
  67. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  68. package/site/content/docs/plugins/magic-link.mdx +6 -0
  69. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  70. package/site/content/docs/plugins/oauth.mdx +1 -1
  71. package/site/content/docs/plugins/otp.mdx +12 -1
  72. package/site/content/docs/plugins/passkey.mdx +39 -15
  73. package/site/content/docs/plugins/two-factor.mdx +91 -34
  74. package/site/content/docs/plugins/username.mdx +1 -1
  75. package/site/content/docs/recipes/caddy.mdx +3 -3
  76. package/site/content/docs/recipes/index.mdx +18 -28
  77. package/site/content/docs/recipes/mailpit.mdx +1 -1
  78. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  79. package/site/content/docs/recipes/meta.json +1 -4
  80. package/site/content/docs/recipes/nginx.mdx +2 -2
  81. package/site/content/docs/recipes/openrouter.mdx +283 -0
  82. package/site/content/docs/recipes/pgdog.mdx +3 -3
  83. package/site/content/docs/recipes/postgres.mdx +1 -2
  84. package/site/content/docs/recipes/rustfs.mdx +2 -2
  85. package/site/content/docs/recipes/traefik.mdx +3 -3
  86. package/site/content/docs/reference/cli.mdx +231 -0
  87. package/site/content/docs/reference/configuration.mdx +30 -10
  88. package/site/content/docs/reference/environment-variables.mdx +93 -20
  89. package/site/content/docs/reference/errors.mdx +132 -29
  90. package/site/content/docs/reference/fx.mdx +137 -73
  91. package/site/content/docs/reference/index.mdx +13 -21
  92. package/site/content/docs/reference/meta.json +4 -5
  93. package/site/content/docs/reference/okid.mdx +42 -17
  94. package/site/content/docs/reference/plugins.mdx +4 -3
  95. package/site/content/docs/reference/security.mdx +197 -0
  96. package/site/content/docs/understand/meta.json +5 -0
  97. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  98. package/site/content/docs/understand/the-model.mdx +32 -0
  99. package/site/content/docs/understand/the-problem.mdx +74 -0
  100. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  101. package/src/auth/api-keys.ts +2 -1
  102. package/src/auth/bindings.ts +64 -16
  103. package/src/auth/gate-auth.test.ts +6 -1
  104. package/src/auth/identity.ts +152 -5
  105. package/src/auth/index.ts +29 -0
  106. package/src/auth/invites.ts +2 -1
  107. package/src/auth/method-context.ts +41 -0
  108. package/src/auth/oauth-as/crypto.ts +2 -1
  109. package/src/auth/oauth-as/stores.ts +2 -1
  110. package/src/auth/operator.ts +2 -1
  111. package/src/auth/sessions-jwt.test.ts +77 -0
  112. package/src/auth/sessions.ts +20 -4
  113. package/src/auth/tenants.ts +3 -2
  114. package/src/auth/two-factor-challenge.test.ts +61 -0
  115. package/src/auth/two-factor-challenge.ts +284 -0
  116. package/src/auth/verification.ts +5 -0
  117. package/src/bench/REPORT.md +49 -0
  118. package/src/bench/g03-signal-once.bench.ts +1 -1
  119. package/src/bench/g10-observability-contention.bench.ts +1 -1
  120. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  121. package/src/bench/load-app.ts +2 -10
  122. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  123. package/src/cli/ai-setup/apply.ts +294 -52
  124. package/src/cli/ai-setup/catalog.ts +238 -1342
  125. package/src/cli/ai-setup/index.ts +14 -99
  126. package/src/cli/ai-setup/prompts.ts +60 -443
  127. package/src/cli/ask-seed.test.ts +96 -0
  128. package/src/cli/ask-seed.ts +82 -0
  129. package/src/cli/ask-vault-gaps.test.ts +84 -0
  130. package/src/cli/ask-vault-gaps.ts +146 -0
  131. package/src/cli/build.ts +2 -2
  132. package/src/cli/client-add.test.ts +26 -1
  133. package/src/cli/client-add.ts +106 -27
  134. package/src/cli/competitor-mention-removal.test.ts +1 -1
  135. package/src/cli/db-seed.ts +2 -0
  136. package/src/cli/db.ts +150 -11
  137. package/src/cli/dev.ts +119 -211
  138. package/src/cli/docker-clean.ts +2 -2
  139. package/src/cli/docker-cli.test.ts +1 -1
  140. package/src/cli/doctor-diff.ts +4 -2
  141. package/src/cli/doctor-pii.test.ts +1 -1
  142. package/src/cli/doctor.ts +14 -1
  143. package/src/cli/hero-meta.test.ts +4 -2
  144. package/src/cli/load-config.images.test.ts +8 -10
  145. package/src/cli/load-config.ts +1 -1
  146. package/src/cli/registry.ts +22 -10
  147. package/src/cli/replay.ts +3 -1
  148. package/src/cli/start.ts +1 -1
  149. package/src/cli/tui/keys.ts +2 -3
  150. package/src/client/auth/cookies.ts +74 -0
  151. package/src/client/auth/create-auth-client.ts +636 -0
  152. package/src/client/auth/denials.ts +99 -0
  153. package/src/client/auth/session.ts +283 -0
  154. package/src/client/auth.test.ts +251 -0
  155. package/src/client/auth.ts +39 -114
  156. package/src/client/create-with-session.ts +245 -0
  157. package/src/client/create.ts +51 -5
  158. package/src/client/index.ts +11 -1
  159. package/src/client/live.ts +11 -79
  160. package/src/client/notes-contract.test.ts +5 -10
  161. package/src/client/sse.ts +137 -0
  162. package/src/client/stream.ts +147 -0
  163. package/src/client/transport.test.ts +32 -0
  164. package/src/client/transport.ts +123 -26
  165. package/src/client/types.ts +118 -14
  166. package/src/client-react/index.ts +185 -24
  167. package/src/client-react/use-live-query.ts +13 -3
  168. package/src/compiler/aot.test.ts +7 -4
  169. package/src/compiler/effects-embed.test.ts +56 -0
  170. package/src/compiler/effects-fetch.test.ts +43 -0
  171. package/src/compiler/effects-infer.ts +58 -2
  172. package/src/compiler/extract.test.ts +258 -44
  173. package/src/compiler/extract.ts +566 -48
  174. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  175. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  176. package/src/compiler/fixtures/skyport.expected.json +4 -4
  177. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  178. package/src/compiler/response.ts +2 -2
  179. package/src/compiler/search-writer-isolation.test.ts +40 -0
  180. package/src/config/index.ts +11 -0
  181. package/src/console/server/app.ts +17 -23
  182. package/src/console/server/bind.ts +4 -0
  183. package/src/console/server/console.test.ts +2 -2
  184. package/src/console/server/flows-invoke.test.ts +50 -34
  185. package/src/console/server/flows.ts +436 -255
  186. package/src/console/server/runs-ingest.test.ts +6 -3
  187. package/src/console/server/serve.ts +1 -1
  188. package/src/console/server/signals.test.ts +1 -5
  189. package/src/console/server/signals.ts +8 -6
  190. package/src/console/server/state.ts +6 -1
  191. package/src/console/server/store.ts +1 -1
  192. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  193. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  194. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  195. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  196. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  197. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  198. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  199. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  200. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  201. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  202. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  203. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  204. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  205. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  206. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  207. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  208. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  209. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  210. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  211. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  212. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  213. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  214. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  215. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  216. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  217. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  218. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  219. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  220. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  221. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  222. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  223. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  224. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  225. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  226. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  227. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  228. package/src/console/ui-next/dist/index.html +6 -13
  229. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  230. package/src/console/ui-next/src/client.ts +7 -2
  231. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  232. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  233. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  235. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  236. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  237. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  238. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  239. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  240. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  241. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  242. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  243. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  244. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  245. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  246. package/src/docker/ai-model-status.test.ts +1 -12
  247. package/src/docker/ai-model-status.ts +10 -38
  248. package/src/docker/compose-up.test.ts +100 -0
  249. package/src/docker/compose-up.ts +292 -0
  250. package/src/docker/compose.ts +4 -14
  251. package/src/docker/derive.ts +9 -34
  252. package/src/docker/docker.test.ts +16 -261
  253. package/src/docker/images-config.test.ts +13 -25
  254. package/src/docker/index.ts +1 -24
  255. package/src/docker/recipes/index.ts +0 -23
  256. package/src/docker/recipes/pgdog.ts +6 -4
  257. package/src/docker/stack-id.test.ts +2 -2
  258. package/src/docker/stack-id.ts +0 -1
  259. package/src/docker/types.ts +2 -2
  260. package/src/drivers/ai-anthropic.ts +10 -0
  261. package/src/drivers/ai-openai-compatible.ts +34 -7
  262. package/src/drivers/ai-providers.test.ts +0 -107
  263. package/src/drivers/ai-stream.test.ts +0 -34
  264. package/src/drivers/ai-types.ts +17 -8
  265. package/src/drivers/channel-fcm.ts +6 -1
  266. package/src/drivers/channel-msegat.ts +6 -1
  267. package/src/drivers/channel-resend.ts +5 -1
  268. package/src/drivers/channel-smtp.ts +5 -1
  269. package/src/drivers/channel-sndr.ts +10 -1
  270. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  271. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  272. package/src/drivers/channel-taqnyat.ts +6 -1
  273. package/src/drivers/channel-types.ts +10 -0
  274. package/src/drivers/channel-unifonic.ts +6 -1
  275. package/src/drivers/channel-wa-cloud.ts +6 -1
  276. package/src/drivers/channel-webpush.ts +6 -1
  277. package/src/drivers/external.ts +33 -0
  278. package/src/drivers/index.ts +0 -10
  279. package/src/drivers/meilisearch.ts +6 -0
  280. package/src/drivers/oauth-types.ts +4 -0
  281. package/src/drivers/postgres.test.ts +74 -3
  282. package/src/drivers/postgres.ts +175 -6
  283. package/src/drivers/signal-types.ts +5 -5
  284. package/src/drivers/types.ts +7 -0
  285. package/src/drivers/vault-types.ts +5 -0
  286. package/src/elements/ai/declare.ts +17 -4
  287. package/src/elements/ai/eval.ts +1 -1
  288. package/src/elements/ai/mcp-http.ts +1 -1
  289. package/src/elements/ai/pii.ts +1 -1
  290. package/src/elements/ai/providers.test.ts +289 -0
  291. package/src/elements/ai/providers.ts +169 -0
  292. package/src/elements/ai/runtime.ts +68 -5
  293. package/src/elements/ai/schema.ts +2 -2
  294. package/src/elements/ai.test.ts +7 -7
  295. package/src/elements/ai.ts +17 -1
  296. package/src/elements/channel/runtime.ts +105 -12
  297. package/src/elements/clock/cron-fields.test.ts +144 -0
  298. package/src/elements/clock/cron-fields.ts +185 -0
  299. package/src/elements/clock/declare.ts +247 -3
  300. package/src/elements/clock.ts +21 -1
  301. package/src/elements/index.ts +15 -0
  302. package/src/elements/signal/chaos-child.ts +1 -2
  303. package/src/elements/signal/declare.ts +65 -37
  304. package/src/elements/signal/delivery-modes.test.ts +11 -31
  305. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  306. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  307. package/src/elements/signal/key-ordering.test.ts +6 -26
  308. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  309. package/src/elements/signal/optional-emit.test.ts +6 -9
  310. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  311. package/src/elements/signal/orphan-messages.test.ts +4 -15
  312. package/src/elements/signal/reconcile.test.ts +2 -2
  313. package/src/elements/signal/runtime.ts +1 -1
  314. package/src/elements/signal/schema-emit.test.ts +4 -8
  315. package/src/elements/signal.test.ts +24 -49
  316. package/src/elements/signal.ts +9 -2
  317. package/src/elements/store/domain-ddl.test.ts +1 -1
  318. package/src/elements/store/live-http.test.ts +9 -4
  319. package/src/elements/store/prepare-row.test.ts +123 -0
  320. package/src/elements/store/resource.ts +43 -12
  321. package/src/elements/store/schema-decl.ts +117 -0
  322. package/src/elements/store/search-backfill.ts +205 -0
  323. package/src/elements/store/search-bind.ts +66 -0
  324. package/src/elements/store/search-bm25.ts +78 -0
  325. package/src/elements/store/search-ddl.ts +156 -0
  326. package/src/elements/store/search-embed-flow.ts +141 -0
  327. package/src/elements/store/search-errors.ts +34 -0
  328. package/src/elements/store/search-fusion.ts +112 -0
  329. package/src/elements/store/search-lsh.ts +179 -0
  330. package/src/elements/store/search-runtime.ts +300 -0
  331. package/src/elements/store/search.test.ts +144 -0
  332. package/src/elements/store/sql-session.test.ts +1 -0
  333. package/src/elements/store/sql-session.ts +83 -3
  334. package/src/elements/store/table.ts +23 -2
  335. package/src/elements/store/upsert-app.test.ts +1 -3
  336. package/src/elements/store.ts +50 -0
  337. package/src/full.ts +4 -2
  338. package/src/http.ts +4 -1
  339. package/src/i18n/catalogs/ar.ts +14 -6
  340. package/src/i18n/catalogs/en.ts +14 -6
  341. package/src/index.ts +6 -1
  342. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  343. package/src/kernel/adopt-routes.ts +9 -1
  344. package/src/kernel/app-auth.ts +5 -0
  345. package/src/kernel/app.ts +102 -9
  346. package/src/kernel/auto-cache.test.ts +6 -12
  347. package/src/kernel/auto-registry.test.ts +3 -3
  348. package/src/kernel/boot-bind/ai.test.ts +22 -19
  349. package/src/kernel/boot-bind/ai.ts +5 -43
  350. package/src/kernel/boot-bind/clock.ts +23 -5
  351. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  352. package/src/kernel/boot.test.ts +11 -6
  353. package/src/kernel/boot.ts +66 -16
  354. package/src/kernel/boundary-contract.ts +93 -0
  355. package/src/kernel/budget.test.ts +1 -1
  356. package/src/kernel/call.ts +89 -0
  357. package/src/kernel/capability.ts +6 -0
  358. package/src/kernel/client-descriptor.ts +123 -0
  359. package/src/kernel/clock-timezone.test.ts +80 -0
  360. package/src/kernel/correlation.test.ts +1 -1
  361. package/src/kernel/dry-run.ts +11 -8
  362. package/src/kernel/effects-stamping.test.ts +42 -9
  363. package/src/kernel/effects.test.ts +4 -2
  364. package/src/kernel/effects.ts +51 -9
  365. package/src/kernel/errors-channel.ts +17 -0
  366. package/src/kernel/errors-live-resume.ts +3 -2
  367. package/src/kernel/errors-tenant.ts +7 -4
  368. package/src/kernel/errors.registry-helpers.ts +96 -0
  369. package/src/kernel/errors.registry.test.ts +139 -25
  370. package/src/kernel/errors.ts +102 -23
  371. package/src/kernel/external-effects.test.ts +196 -0
  372. package/src/kernel/flow.test.ts +13 -4
  373. package/src/kernel/flow.ts +69 -71
  374. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  375. package/src/kernel/fx-dead-letters.test.ts +3 -3
  376. package/src/kernel/fx-fetch.ts +55 -0
  377. package/src/kernel/fx-live-stream.ts +2 -2
  378. package/src/kernel/fx-live.test.ts +8 -8
  379. package/src/kernel/fx.test.ts +17 -11
  380. package/src/kernel/fx.ts +180 -57
  381. package/src/kernel/horizontal-child.ts +1 -1
  382. package/src/kernel/http-query.test.ts +3 -6
  383. package/src/kernel/index.ts +13 -3
  384. package/src/kernel/instance-id.ts +1 -1
  385. package/src/kernel/instances.test.ts +40 -0
  386. package/src/kernel/instances.ts +47 -9
  387. package/src/kernel/live-http.test.ts +6 -7
  388. package/src/kernel/live-http.ts +6 -2
  389. package/src/kernel/live-resume.test.ts +1 -1
  390. package/src/kernel/mcp-tool.test.ts +3 -3
  391. package/src/kernel/on.ts +98 -4
  392. package/src/kernel/pipeline.test.ts +12 -15
  393. package/src/kernel/plugin-elements.test.ts +1 -1
  394. package/src/kernel/stamp-http.test.ts +3 -3
  395. package/src/kernel/triggers.ts +210 -95
  396. package/src/kernel-entry.ts +0 -1
  397. package/src/manifest/diff.ts +11 -1
  398. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  399. package/src/manifest/types.ts +46 -2
  400. package/src/mcp/docs-index.ts +3 -3
  401. package/src/mcp/docs-mcp.test.ts +2 -2
  402. package/src/mcp/docs-tools.ts +1 -1
  403. package/src/okid.test.ts +56 -0
  404. package/src/okid.ts +55 -11
  405. package/src/plugins/anonymous.ts +8 -4
  406. package/src/plugins/auth/shared.ts +27 -3
  407. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  408. package/src/plugins/auth-methods.security.test.ts +561 -23
  409. package/src/plugins/config-source.ts +1 -1
  410. package/src/plugins/csrf.test.ts +65 -0
  411. package/src/plugins/index.ts +2 -0
  412. package/src/plugins/magic-link.ts +36 -16
  413. package/src/plugins/oauth/shared.ts +3 -1
  414. package/src/plugins/oauth.ts +22 -14
  415. package/src/plugins/otp.ts +37 -21
  416. package/src/plugins/passkey-webauthn.ts +9 -6
  417. package/src/plugins/passkey.ts +98 -26
  418. package/src/plugins/pre-account-hijack.test.ts +223 -0
  419. package/src/plugins/two-factor.ts +466 -37
  420. package/src/plugins/username.ts +19 -7
  421. package/src/release/build-lib.ts +1 -0
  422. package/src/release/limits.ts +2 -2
  423. package/src/release/measure.ts +1 -1
  424. package/src/runs/collect.test.ts +0 -2
  425. package/src/runtime/json-code-block.test.ts +250 -20
  426. package/src/runtime/json-code-block.ts +1261 -41
  427. package/src/term.test.ts +1 -1
  428. package/src/test/create-test-app.test.ts +12 -12
  429. package/src/test/live-signals.test.ts +6 -6
  430. package/src/test/provisions.integration.test.ts +10 -14
  431. package/src/test/reset-element-registries.ts +1 -1
  432. package/src/test/tenant-isolation.test.ts +12 -6
  433. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  434. package/site/content/docs/deployment/docker.mdx +0 -227
  435. package/site/content/docs/deployment/index.mdx +0 -83
  436. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  437. package/site/content/docs/deployment/meta.json +0 -5
  438. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  439. package/site/content/docs/elements/ai.mdx +0 -385
  440. package/site/content/docs/elements/channel.mdx +0 -346
  441. package/site/content/docs/elements/clock.mdx +0 -244
  442. package/site/content/docs/elements/flow.mdx +0 -420
  443. package/site/content/docs/elements/gate.mdx +0 -436
  444. package/site/content/docs/elements/signal.mdx +0 -380
  445. package/site/content/docs/elements/store.mdx +0 -1099
  446. package/site/content/docs/elements/vault.mdx +0 -405
  447. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  448. package/site/content/docs/get-started/index.mdx +0 -43
  449. package/site/content/docs/get-started/installation.mdx +0 -220
  450. package/site/content/docs/get-started/introduction.mdx +0 -132
  451. package/site/content/docs/get-started/meta.json +0 -13
  452. package/site/content/docs/get-started/project-structure.mdx +0 -925
  453. package/site/content/docs/get-started/testing.mdx +0 -328
  454. package/site/content/docs/get-started/why.mdx +0 -162
  455. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  456. package/site/content/docs/recipes/ollama.mdx +0 -142
  457. package/site/content/docs/recipes/sglang.mdx +0 -105
  458. package/site/content/docs/recipes/vllm.mdx +0 -106
  459. package/site/content/docs/reference/cli.md +0 -232
  460. package/site/content/docs/reference/client.mdx +0 -469
  461. package/site/content/docs/reference/security.md +0 -74
  462. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  463. package/src/cli/ai-setup/recommend.test.ts +0 -225
  464. package/src/cli/ai-setup/recommend.ts +0 -225
  465. package/src/cli/dev-controls.test.ts +0 -122
  466. package/src/cli/dev-controls.ts +0 -164
  467. package/src/cli/tui/DevLive.tsx +0 -126
  468. package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
  469. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  470. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  471. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  472. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  473. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  474. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  475. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  476. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  477. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  478. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  479. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  480. package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
  481. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  482. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  483. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  484. package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
  487. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  488. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  489. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  490. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  491. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  492. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  493. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  494. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  495. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  496. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  497. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  498. package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
  499. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  500. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  501. package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
  502. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  503. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  504. package/src/docker/ollama-pull.ts +0 -232
  505. package/src/docker/recipes/llama-cpp.ts +0 -298
  506. package/src/docker/recipes/ollama.ts +0 -44
  507. package/src/docker/recipes/sglang.ts +0 -55
  508. package/src/docker/recipes/vllm.ts +0 -44
  509. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  510. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  511. package/src/drivers/ai-ollama.ts +0 -389
  512. package/src/drivers/ollama.ts +0 -14
@@ -1,109 +0,0 @@
1
- /**
2
- * Live Ollama tool-calling through fx.ask → fx.call (capability + ledger).
3
- *
4
- * Opt-in via `OKE_TEST_OLLAMA_URL` (reachable Ollama). A local daemon on
5
- * :11434 is not enough — set the URL explicitly. Never an empty pass.
6
- */
7
-
8
- import { describe, expect, test } from "bun:test";
9
- import { memoryKvDriver } from "./index.ts";
10
- import { ai, aiRateGate, createAiRuntime } from "../elements/ai.ts";
11
- import { createGateRuntime, gate } from "../elements/gate.ts";
12
- import { createFxContext } from "../kernel/fx.ts";
13
- import { OLLAMA_DEFAULT_MODEL, openOllama } from "./ai-ollama.ts";
14
-
15
- const ENV_URL = process.env.OKE_TEST_OLLAMA_URL?.trim();
16
-
17
- async function probeOllama(url: string): Promise<boolean> {
18
- try {
19
- const res = await fetch(`${url}/api/tags`, { signal: AbortSignal.timeout(1_500) });
20
- return res.ok;
21
- } catch {
22
- return false;
23
- }
24
- }
25
-
26
- const reachable = ENV_URL ? await probeOllama(ENV_URL) : false;
27
- const canLive = Boolean(ENV_URL) && reachable;
28
-
29
- if (!canLive) {
30
- console.log(
31
- ENV_URL
32
- ? `skip: live ollama tool-calling e2e (unreachable at ${ENV_URL})`
33
- : "skip: live ollama tool-calling e2e (OKE_TEST_OLLAMA_URL not set)",
34
- );
35
- }
36
- const live = canLive ? test : test.skip;
37
-
38
- describe("ollama live — tool-calling via fx.call", () => {
39
- live(
40
- "gated ask → real tool call → capability ledger portal entry",
41
- async () => {
42
- const url = ENV_URL!;
43
- const model = process.env.OKE_AI_MODEL?.trim() || OLLAMA_DEFAULT_MODEL;
44
-
45
- const kv = await memoryKvDriver.open({ name: "ollama-tools-rate" });
46
- const askRate = aiRateGate("ask");
47
- const gates = createGateRuntime({
48
- gates: [gate.public, askRate],
49
- kv,
50
- now: () => Date.now(),
51
- });
52
-
53
- // Simulate HTTP beforeHandle rate gate (AI ask preset).
54
- const allowed = await gates.allow([askRate.name], {
55
- auth: { userId: "user-1", scopes: new Set() },
56
- operator: { id: null },
57
- });
58
- expect(allowed).toBe(true);
59
-
60
- const client = await openOllama({ baseUrl: url, model });
61
- const smart = ai.model("smart", { provider: "ollama", model });
62
- const prompt = smart.prompt("tool-host");
63
- const runtime = createAiRuntime({
64
- models: [smart],
65
- prompts: [prompt],
66
- clients: { smart: client },
67
- forceJournal: false,
68
- });
69
-
70
- const toolInvocations: Array<{ name: string; input: unknown }> = [];
71
- const { fx, ledger } = createFxContext({
72
- flow: "assistant.chat",
73
- effects: {
74
- asks: ["tool-host"],
75
- calls: ["lookup.echo"],
76
- },
77
- aiRuntime: runtime,
78
- callHandler: async (name, input) => {
79
- toolInvocations.push({ name, input });
80
- return { ok: true, echoed: input };
81
- },
82
- });
83
-
84
- await fx.ask(
85
- prompt,
86
- [
87
- "You are a tool-using assistant.",
88
- 'You MUST call the function lookup.echo with arguments {"text":"pong"}.',
89
- "Do not answer in plain text until after the tool has been called.",
90
- ].join(" "),
91
- { tools: ["lookup.echo"], maxSteps: 4 },
92
- );
93
-
94
- expect(toolInvocations.length).toBeGreaterThan(0);
95
- expect(toolInvocations.some((c) => c.name === "lookup.echo")).toBe(true);
96
- expect(ledger.entries.some((e) => e.kind === "ask" && e.resource === "tool-host")).toBe(true);
97
- expect(
98
- ledger.entries.some(
99
- (e) => e.kind === "call" && e.resource === "lookup.echo" && e.reversibility === "portal",
100
- ),
101
- ).toBe(true);
102
-
103
- await kv.close();
104
- },
105
- // Match ai-ollama.integration.test.ts — cold model load on a 6GB+ pull
106
- // can exceed a short CI wall clock even when the hot path is ~7s.
107
- 25 * 60_000,
108
- );
109
- });
@@ -1,184 +0,0 @@
1
- /**
2
- * Live Ollama round-trip — real prompt, real response, documented default model.
3
- *
4
- * Opt-in (never auto-pull a multi-GB model in ordinary `bun test`):
5
- * 1. `OKE_TEST_OLLAMA_URL` — point at any reachable Ollama
6
- * 2. `OKE_TEST_OLLAMA_DOCKER=1` — start `ollama/ollama` via the recipe; host-side
7
- * `ensureOllamaModel` POSTs `/api/pull` for `qwen3.5:9b` (slow first run)
8
- *
9
- * Without one of those, the suite skips with a visible reason (never an empty pass).
10
- * A local daemon on :11434 is not enough — set `OKE_TEST_OLLAMA_URL` explicitly.
11
- */
12
-
13
- import { afterAll, describe, expect, test } from "bun:test";
14
- import { mkdtemp, rm } from "node:fs/promises";
15
- import { tmpdir } from "node:os";
16
- import { join } from "node:path";
17
- import { deriveInfrastructure, writeDerivedFiles } from "../docker/index.ts";
18
- import { ai } from "../elements/ai.ts";
19
- import { bindAi } from "../kernel/boot-bind/ai.ts";
20
- import { OLLAMA_DEFAULT_MODEL, openOllama } from "./ai-ollama.ts";
21
-
22
- const OLLAMA_IMAGE = "ollama/ollama:latest";
23
- const ENV_URL = process.env.OKE_TEST_OLLAMA_URL?.trim();
24
- const WANT_DOCKER = process.env.OKE_TEST_OLLAMA_DOCKER === "1";
25
-
26
- function dockerAvailable(): boolean {
27
- try {
28
- return Bun.spawnSync(["docker", "info"], { stdout: "pipe", stderr: "pipe" }).exitCode === 0;
29
- } catch {
30
- return false;
31
- }
32
- }
33
-
34
- const DOCKER = dockerAvailable();
35
- const canLive = Boolean(ENV_URL) || (WANT_DOCKER && DOCKER);
36
-
37
- if (!canLive) {
38
- const reasons: string[] = [];
39
- if (!ENV_URL) reasons.push("OKE_TEST_OLLAMA_URL not set");
40
- if (!WANT_DOCKER) reasons.push("OKE_TEST_OLLAMA_DOCKER≠1");
41
- else if (!DOCKER) reasons.push("docker daemon not available");
42
- console.log(`skip: live ollama e2e (${reasons.join("; ")})`);
43
- }
44
- const live = canLive ? test : test.skip;
45
-
46
- let cleanup: (() => Promise<void>) | undefined;
47
-
48
- afterAll(async () => {
49
- if (cleanup) await cleanup();
50
- });
51
-
52
- async function waitForOllama(url: string, timeoutMs: number, requireModel: boolean): Promise<void> {
53
- const deadline = Date.now() + timeoutMs;
54
- while (Date.now() < deadline) {
55
- try {
56
- const res = await fetch(`${url}/api/tags`);
57
- if (res.ok) {
58
- if (!requireModel) return;
59
- const body = (await res.json()) as { models?: readonly { name?: string }[] };
60
- const names = (body.models ?? []).map((m) => m.name ?? "");
61
- if (
62
- names.some((n) => n === OLLAMA_DEFAULT_MODEL || n.startsWith(`${OLLAMA_DEFAULT_MODEL}`))
63
- ) {
64
- return;
65
- }
66
- }
67
- } catch {
68
- // still starting
69
- }
70
- await Bun.sleep(1_000);
71
- }
72
- throw new Error(
73
- `ollama not ready at ${url} within ${timeoutMs}ms${
74
- requireModel ? ` (need model ${OLLAMA_DEFAULT_MODEL})` : ""
75
- }`,
76
- );
77
- }
78
-
79
- async function resolveLiveUrl(): Promise<{ url: string; model: string }> {
80
- if (ENV_URL) {
81
- await waitForOllama(ENV_URL, 30_000, false);
82
- return { url: ENV_URL, model: process.env.OKE_AI_MODEL?.trim() || OLLAMA_DEFAULT_MODEL };
83
- }
84
-
85
- const dir = await mkdtemp(join(tmpdir(), "oke-ollama-"));
86
- const dockerDir = join(dir, "docker");
87
- const project = `oke-ollama-${Date.now()}`;
88
- const instanceId = crypto.randomUUID().replace(/-/g, "").slice(0, 6);
89
- const derived = deriveInfrastructure({
90
- images: { ai: OLLAMA_IMAGE },
91
- app: "ollama-e2e",
92
- host: "127.0.0.1",
93
- includeApp: false,
94
- composeDir: "docker",
95
- instanceId,
96
- controls: { OKE_AI_MODEL: OLLAMA_DEFAULT_MODEL },
97
- });
98
- await writeDerivedFiles(derived, dockerDir, { writeStackEnv: true });
99
-
100
- const composeFiles = ["compose.yml", "compose.ai.yml"];
101
- const up = Bun.spawn(
102
- ["docker", "compose", "-p", project, ...composeFiles.flatMap((f) => ["-f", f]), "up", "-d"],
103
- {
104
- cwd: dockerDir,
105
- stdout: "pipe",
106
- stderr: "pipe",
107
- env: { ...process.env, ...derived.stackEnv, OKE_AI_MODEL: OLLAMA_DEFAULT_MODEL },
108
- },
109
- );
110
- const [upErr, upCode] = await Promise.all([new Response(up.stderr).text(), up.exited]);
111
- if (upCode !== 0) {
112
- await rm(dir, { recursive: true, force: true });
113
- throw new Error(`docker compose up failed: ${upErr}`);
114
- }
115
-
116
- const url = derived.stackEnv.OKE_AI_URL!;
117
- cleanup = async () => {
118
- const down = Bun.spawn(
119
- ["docker", "compose", "-p", project, ...composeFiles.flatMap((f) => ["-f", f]), "down", "-v"],
120
- { cwd: dockerDir, stdout: "pipe", stderr: "pipe" },
121
- );
122
- await down.exited;
123
- await rm(dir, { recursive: true, force: true });
124
- };
125
-
126
- // Serve must answer before we POST /api/pull (same path as `oke dev -d`).
127
- await waitForOllama(url, 120_000, false);
128
- const { ensureOllamaModel } = await import("../docker/ollama-pull.ts");
129
- await ensureOllamaModel({ url, model: OLLAMA_DEFAULT_MODEL });
130
- await waitForOllama(url, 60_000, true);
131
- return { url, model: OLLAMA_DEFAULT_MODEL };
132
- }
133
-
134
- describe("ollama live — real completion", () => {
135
- live(
136
- "boots against a real Ollama, completes with a real model, returns a real response",
137
- async () => {
138
- const { url, model } = await resolveLiveUrl();
139
- const prevUrl = process.env.OKE_AI_URL;
140
- const prevModel = process.env.OKE_AI_MODEL;
141
- process.env.OKE_AI_URL = url;
142
- process.env.OKE_AI_MODEL = model;
143
-
144
- try {
145
- const smart = ai.model("smart", { provider: "ollama", model });
146
- const ping = smart.prompt("ollama-ping", { version: 1 });
147
-
148
- const runtime = bindAi(
149
- {
150
- config: { drivers: { ai: { test: "ollama", dev: "ollama" } } },
151
- ai: { models: [smart], prompts: [ping] },
152
- },
153
- undefined,
154
- () => Date.now(),
155
- "test",
156
- false,
157
- );
158
- expect(runtime.prompts.has("ollama-ping")).toBe(true);
159
-
160
- const client = await openOllama({ baseUrl: url, model });
161
- const result = await client.complete({
162
- messages: [
163
- {
164
- role: "user",
165
- content: "Reply with exactly the single word: pong",
166
- },
167
- ],
168
- temperature: 0,
169
- maxTokens: 32,
170
- });
171
-
172
- expect(result.driverId).toBe("ollama");
173
- expect(result.text.trim().length).toBeGreaterThan(0);
174
- expect(result.text.toLowerCase()).toContain("pong");
175
- } finally {
176
- if (prevUrl === undefined) delete process.env.OKE_AI_URL;
177
- else process.env.OKE_AI_URL = prevUrl;
178
- if (prevModel === undefined) delete process.env.OKE_AI_MODEL;
179
- else process.env.OKE_AI_MODEL = prevModel;
180
- }
181
- },
182
- 25 * 60_000,
183
- );
184
- });
@@ -1,389 +0,0 @@
1
- /**
2
- * `ollama` AI driver — thin fetch client for the native Ollama HTTP API.
3
- *
4
- * Any pulled model works via `model` / `OKE_AI_MODEL`. The documented local-dev
5
- * default is `qwen3.5:9b` (balanced starting point — override freely; on Apple
6
- * Silicon consider `qwen3.5:9b-mlx`). Fail-loud:
7
- * configured but unreachable throws {@link OllamaUnavailableError} — never a
8
- * silent mock fallback.
9
- *
10
- * Native API: `POST /api/chat` and `POST /api/embeddings` (not the OpenAI-compat
11
- * shim). Default base URL `http://127.0.0.1:11434`. Supports `complete`,
12
- * `stream` (NDJSON), `embed`, and tools.
13
- */
14
-
15
- import type {
16
- AiCompleteOptions,
17
- AiCompleteResult,
18
- AiDriver,
19
- AiEmbedOptions,
20
- AiEmbedResult,
21
- AiModelClient,
22
- AiOpenOptions,
23
- AiStreamChunk,
24
- AiToolCall,
25
- } from "./ai-types.ts";
26
-
27
- /** Documented local-dev default — override via `model` / `OKE_AI_MODEL`. */
28
- export const OLLAMA_DEFAULT_MODEL = "qwen3.5:9b";
29
-
30
- /** Default Ollama listen URL (host installs + compose host port). */
31
- export const OLLAMA_DEFAULT_BASE_URL = "http://127.0.0.1:11434";
32
-
33
- /** Error thrown when Ollama is unreachable / unhealthy / rejects a call. */
34
- export class OllamaUnavailableError extends Error {
35
- constructor(message: string) {
36
- super(message);
37
- this.name = "OllamaUnavailableError";
38
- }
39
- }
40
-
41
- /**
42
- * Normalize a base URL or bare `host:port` (as in `OLLAMA_HOST`) to an origin.
43
- *
44
- * @param raw - URL or host:port
45
- */
46
- export function normalizeOllamaBaseUrl(raw: string): string {
47
- const trimmed = raw.trim().replace(/\/$/, "");
48
- if (!trimmed) return OLLAMA_DEFAULT_BASE_URL;
49
- if (/^https?:\/\//i.test(trimmed)) return trimmed.replace(/\/v1\/?$/i, "");
50
- return `http://${trimmed}`;
51
- }
52
-
53
- /**
54
- * Resolve the Ollama base URL from options / env (never invent a cloud endpoint).
55
- *
56
- * @param options - Open options
57
- */
58
- export function resolveOllamaBaseUrl(options: AiOpenOptions = {}): string {
59
- const raw =
60
- options.baseUrl?.trim() ||
61
- process.env.OKE_AI_URL?.trim() ||
62
- process.env.OLLAMA_HOST?.trim() ||
63
- OLLAMA_DEFAULT_BASE_URL;
64
- return normalizeOllamaBaseUrl(raw);
65
- }
66
-
67
- /**
68
- * Resolve the model name — fully configurable; documented default only.
69
- *
70
- * @param options - Open options
71
- * @param override - Per-call model override
72
- */
73
- export function resolveOllamaModel(options: AiOpenOptions = {}, override?: string): string {
74
- return (
75
- override?.trim() ||
76
- options.model?.trim() ||
77
- process.env.OKE_AI_MODEL?.trim() ||
78
- OLLAMA_DEFAULT_MODEL
79
- );
80
- }
81
-
82
- /**
83
- * Open an Ollama chat client. Health-checks `/api/tags` before returning.
84
- *
85
- * @param options - model / baseUrl / injectable fetch
86
- */
87
- export async function openOllama(options: AiOpenOptions = {}): Promise<AiModelClient> {
88
- const baseUrl = resolveOllamaBaseUrl(options);
89
- const model = resolveOllamaModel(options);
90
- const fetchFn = options.fetch ?? globalThis.fetch;
91
-
92
- await healthCheck(baseUrl, fetchFn);
93
-
94
- return {
95
- driverId: "ollama",
96
- model,
97
- async complete(opts: AiCompleteOptions): Promise<AiCompleteResult> {
98
- const resolvedModel = resolveOllamaModel(options, opts.model);
99
- const body = buildChatBody(resolvedModel, opts, false);
100
-
101
- let res: Response;
102
- try {
103
- res = await fetchFn(`${baseUrl}/api/chat`, {
104
- method: "POST",
105
- headers: { "content-type": "application/json" },
106
- body: JSON.stringify(body),
107
- ...(opts.signal !== undefined ? { signal: opts.signal } : {}),
108
- });
109
- } catch (err) {
110
- if (isAbortError(err)) throw err;
111
- throw new OllamaUnavailableError(
112
- `ollama: unreachable at ${baseUrl} — ${err instanceof Error ? err.message : String(err)}`,
113
- );
114
- }
115
-
116
- const raw = (await res.json().catch(() => ({}))) as OllamaChatResponse;
117
- if (!res.ok) {
118
- const msg = raw.error ?? `ollama HTTP ${res.status}`;
119
- throw new OllamaUnavailableError(`ollama: ${msg}`);
120
- }
121
-
122
- const text = raw.message?.content ?? "";
123
- const toolCalls = parseOllamaToolCalls(raw.message?.tool_calls);
124
- return {
125
- text,
126
- raw,
127
- model: raw.model ?? resolvedModel,
128
- driverId: "ollama",
129
- ...(toolCalls !== undefined ? { toolCalls } : {}),
130
- usage: {
131
- inputTokens: raw.prompt_eval_count,
132
- outputTokens: raw.eval_count,
133
- },
134
- };
135
- },
136
- async *stream(opts: AiCompleteOptions): AsyncIterable<AiStreamChunk> {
137
- const resolvedModel = resolveOllamaModel(options, opts.model);
138
- const body = buildChatBody(resolvedModel, opts, true);
139
-
140
- let res: Response;
141
- try {
142
- res = await fetchFn(`${baseUrl}/api/chat`, {
143
- method: "POST",
144
- headers: { "content-type": "application/json" },
145
- body: JSON.stringify(body),
146
- ...(opts.signal !== undefined ? { signal: opts.signal } : {}),
147
- });
148
- } catch (err) {
149
- if (isAbortError(err)) throw err;
150
- throw new OllamaUnavailableError(
151
- `ollama: unreachable at ${baseUrl} — ${err instanceof Error ? err.message : String(err)}`,
152
- );
153
- }
154
- if (!res.ok) {
155
- const raw = (await res.json().catch(() => ({}))) as OllamaChatResponse;
156
- const msg = raw.error ?? `ollama HTTP ${res.status}`;
157
- throw new OllamaUnavailableError(`ollama: ${msg}`);
158
- }
159
- yield* readOllamaNdjson(res, opts.signal);
160
- },
161
- async embed(opts: AiEmbedOptions): Promise<AiEmbedResult> {
162
- const resolvedModel = resolveOllamaModel(options, opts.model);
163
- const inputs = Array.isArray(opts.input) ? opts.input : [opts.input];
164
- const vectors: number[][] = [];
165
-
166
- for (const prompt of inputs) {
167
- let res: Response;
168
- try {
169
- res = await fetchFn(`${baseUrl}/api/embeddings`, {
170
- method: "POST",
171
- headers: { "content-type": "application/json" },
172
- body: JSON.stringify({ model: resolvedModel, prompt }),
173
- });
174
- } catch (err) {
175
- throw new OllamaUnavailableError(
176
- `ollama: unreachable at ${baseUrl} — ${err instanceof Error ? err.message : String(err)}`,
177
- );
178
- }
179
- const raw = (await res.json().catch(() => ({}))) as OllamaEmbedResponse;
180
- if (!res.ok) {
181
- const msg = raw.error ?? `ollama embed HTTP ${res.status}`;
182
- throw new OllamaUnavailableError(`ollama: ${msg}`);
183
- }
184
- if (!Array.isArray(raw.embedding)) {
185
- throw new OllamaUnavailableError("ollama: embed response missing embedding");
186
- }
187
- vectors.push(raw.embedding);
188
- }
189
-
190
- return {
191
- vectors,
192
- model: resolvedModel,
193
- driverId: "ollama",
194
- };
195
- },
196
- };
197
- }
198
-
199
- /** Protocol-named ollama driver. */
200
- export const ollamaAiDriver: AiDriver = {
201
- id: "ollama",
202
- open: openOllama,
203
- };
204
-
205
- function buildChatBody(
206
- resolvedModel: string,
207
- opts: AiCompleteOptions,
208
- stream: boolean,
209
- ): Record<string, unknown> {
210
- const body: Record<string, unknown> = {
211
- model: resolvedModel,
212
- messages: opts.messages.map((m) => {
213
- const msg: Record<string, unknown> = {
214
- role: m.role,
215
- content: m.content,
216
- };
217
- if (m.name !== undefined) msg.name = m.name;
218
- if (m.toolCalls !== undefined && m.toolCalls.length > 0) {
219
- msg.tool_calls = m.toolCalls.map((tc) => ({
220
- type: "function",
221
- function: {
222
- name: tc.name,
223
- arguments: tc.arguments,
224
- },
225
- }));
226
- }
227
- return msg;
228
- }),
229
- stream,
230
- // Thinking models (e.g. qwen3 / qwen3.5) otherwise spend the token budget in
231
- // `message.thinking` and leave `content` empty — baseline complete
232
- // wants the answer text.
233
- think: false,
234
- };
235
- const modelOptions: Record<string, unknown> = {};
236
- if (opts.temperature !== undefined) modelOptions.temperature = opts.temperature;
237
- if (opts.maxTokens !== undefined) modelOptions.num_predict = opts.maxTokens;
238
- if (Object.keys(modelOptions).length > 0) body.options = modelOptions;
239
- if (opts.responseFormat !== undefined) body.format = opts.responseFormat;
240
- if (opts.tools !== undefined && opts.tools.length > 0) {
241
- body.tools = opts.tools.map((t) => ({
242
- type: "function",
243
- function: {
244
- name: t.name,
245
- ...(t.description !== undefined ? { description: t.description } : {}),
246
- ...(t.parameters !== undefined ? { parameters: t.parameters } : {}),
247
- },
248
- }));
249
- }
250
- return body;
251
- }
252
-
253
- function parseOllamaToolCalls(
254
- raw: readonly OllamaToolCall[] | undefined,
255
- ): readonly AiToolCall[] | undefined {
256
- if (!raw || raw.length === 0) return undefined;
257
- return raw.map((tc, i) => ({
258
- id: `ollama_call_${i}`,
259
- name: tc.function?.name ?? "",
260
- arguments: tc.function?.arguments ?? {},
261
- }));
262
- }
263
-
264
- /**
265
- * Parse Ollama NDJSON stream lines via {@link Bun.JSONL.parseChunk}.
266
- *
267
- * @param res - Streaming response
268
- * @param signal - Optional abort
269
- */
270
- async function* readOllamaNdjson(
271
- res: Response,
272
- signal?: AbortSignal,
273
- ): AsyncGenerator<AiStreamChunk> {
274
- const reader = res.body?.getReader();
275
- if (!reader) {
276
- throw new OllamaUnavailableError("ollama: stream response has no body");
277
- }
278
- const decoder = new TextDecoder();
279
- let pending = "";
280
- try {
281
- while (true) {
282
- if (signal?.aborted) throw abortAsError(signal.reason);
283
- const { done, value } = await reader.read();
284
- if (done) break;
285
- pending += decoder.decode(value, { stream: true });
286
- const parsed = bunJsonl().parseChunk(pending);
287
- pending = pending.slice(parsed.read);
288
- for (const row of parsed.values) {
289
- const chunk = row as OllamaChatResponse;
290
- const delta = chunk.message?.content;
291
- if (typeof delta === "string" && delta.length > 0) {
292
- yield { text: delta };
293
- }
294
- if (chunk.done) {
295
- yield { text: "", done: true };
296
- return;
297
- }
298
- }
299
- if (parsed.error) {
300
- const nl = pending.indexOf("\n");
301
- if (nl === -1) continue;
302
- pending = pending.slice(nl + 1);
303
- }
304
- }
305
- yield { text: "", done: true };
306
- } finally {
307
- reader.releaseLock();
308
- }
309
- }
310
-
311
- interface BunJsonlParse {
312
- readonly values: readonly unknown[];
313
- readonly read: number;
314
- readonly done: boolean;
315
- readonly error: unknown;
316
- }
317
-
318
- function bunJsonl(): {
319
- parse(text: string): unknown[];
320
- parseChunk(text: string): BunJsonlParse;
321
- } {
322
- const jsonl = (
323
- Bun as typeof Bun & {
324
- JSONL?: { parse: (t: string) => unknown[]; parseChunk: (t: string) => BunJsonlParse };
325
- }
326
- ).JSONL;
327
- if (!jsonl) {
328
- throw new Error("ollama: Bun.JSONL is required (Bun >= 1.4.0)");
329
- }
330
- return jsonl;
331
- }
332
-
333
- /**
334
- * Probe Ollama — fail loud before the first completion.
335
- *
336
- * @param baseUrl - Origin
337
- * @param fetchFn - Injectable fetch
338
- */
339
- async function healthCheck(baseUrl: string, fetchFn: typeof globalThis.fetch): Promise<void> {
340
- let res: Response;
341
- try {
342
- res = await fetchFn(`${baseUrl}/api/tags`, { method: "GET" });
343
- } catch (err) {
344
- throw new OllamaUnavailableError(
345
- `ollama: unreachable at ${baseUrl} — ${err instanceof Error ? err.message : String(err)}`,
346
- );
347
- }
348
- if (!res.ok) {
349
- const detail = await res.text().catch(() => "");
350
- throw new OllamaUnavailableError(
351
- `ollama: health check failed at ${baseUrl}/api/tags (${res.status})${
352
- detail ? ` — ${detail.slice(0, 200)}` : ""
353
- }`,
354
- );
355
- }
356
- }
357
-
358
- function isAbortError(err: unknown): boolean {
359
- return err instanceof Error && err.name === "AbortError";
360
- }
361
-
362
- function abortAsError(reason?: unknown): Error {
363
- if (reason instanceof Error) return reason;
364
- const err = new Error(reason !== undefined ? String(reason) : "This operation was aborted");
365
- err.name = "AbortError";
366
- return err;
367
- }
368
-
369
- interface OllamaToolCall {
370
- readonly function?: { readonly name?: string; readonly arguments?: unknown };
371
- }
372
-
373
- interface OllamaChatResponse {
374
- readonly model?: string;
375
- readonly message?: {
376
- readonly role?: string;
377
- readonly content?: string;
378
- readonly tool_calls?: readonly OllamaToolCall[];
379
- };
380
- readonly done?: boolean;
381
- readonly prompt_eval_count?: number;
382
- readonly eval_count?: number;
383
- readonly error?: string;
384
- }
385
-
386
- interface OllamaEmbedResponse {
387
- readonly embedding?: readonly number[];
388
- readonly error?: string;
389
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Protocol-named re-export — prefer `okengine/drivers/ai-ollama` or
3
- * `okengine/drivers` (`ollamaAiDriver`). Same surface as {@link ./ai-ollama.ts}.
4
- */
5
- export {
6
- ollamaAiDriver,
7
- openOllama,
8
- OllamaUnavailableError,
9
- OLLAMA_DEFAULT_MODEL,
10
- OLLAMA_DEFAULT_BASE_URL,
11
- normalizeOllamaBaseUrl,
12
- resolveOllamaBaseUrl,
13
- resolveOllamaModel,
14
- } from "./ai-ollama.ts";