okengine 0.18.5 → 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 (510) 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/llms-txt.mdx +1 -1
  5. package/site/content/docs/ai/meta.json +1 -1
  6. package/site/content/docs/ai/skills.mdx +4 -1
  7. package/site/content/docs/ai/try-it.mdx +58 -0
  8. package/site/content/docs/client/auth.mdx +208 -0
  9. package/site/content/docs/client/calling.mdx +451 -0
  10. package/site/content/docs/client/index.mdx +209 -0
  11. package/site/content/docs/client/live.mdx +234 -0
  12. package/site/content/docs/client/meta.json +5 -0
  13. package/site/content/docs/client/react.mdx +249 -0
  14. package/site/content/docs/elements/ai/agents.mdx +232 -0
  15. package/site/content/docs/elements/ai/index.mdx +341 -0
  16. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  17. package/site/content/docs/elements/ai/meta.json +5 -0
  18. package/site/content/docs/elements/ai/models.mdx +283 -0
  19. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  20. package/site/content/docs/elements/channel/email.mdx +266 -0
  21. package/site/content/docs/elements/channel/index.mdx +344 -0
  22. package/site/content/docs/elements/channel/meta.json +5 -0
  23. package/site/content/docs/elements/channel/push.mdx +218 -0
  24. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  25. package/site/content/docs/elements/channel/sms.mdx +264 -0
  26. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  27. package/site/content/docs/elements/clock/index.mdx +300 -0
  28. package/site/content/docs/elements/clock/meta.json +5 -0
  29. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  30. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  31. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  32. package/site/content/docs/elements/flow/http.mdx +1080 -0
  33. package/site/content/docs/elements/flow/index.mdx +618 -0
  34. package/site/content/docs/elements/flow/meta.json +5 -0
  35. package/site/content/docs/elements/flow/routing.mdx +559 -0
  36. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  37. package/site/content/docs/elements/gate/auth.mdx +371 -0
  38. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  39. package/site/content/docs/elements/gate/index.mdx +418 -0
  40. package/site/content/docs/elements/gate/meta.json +5 -0
  41. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  42. package/site/content/docs/elements/gate/rls.mdx +303 -0
  43. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  44. package/site/content/docs/elements/index.mdx +20 -10
  45. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  46. package/site/content/docs/elements/signal/index.mdx +352 -0
  47. package/site/content/docs/elements/signal/live.mdx +590 -0
  48. package/site/content/docs/elements/signal/meta.json +5 -0
  49. package/site/content/docs/elements/signal/once.mdx +596 -0
  50. package/site/content/docs/elements/store/files.mdx +659 -0
  51. package/site/content/docs/elements/store/index.mdx +310 -0
  52. package/site/content/docs/elements/store/kv.mdx +629 -0
  53. package/site/content/docs/elements/store/meta.json +5 -0
  54. package/site/content/docs/elements/store/search.mdx +946 -0
  55. package/site/content/docs/elements/store/sql.mdx +937 -0
  56. package/site/content/docs/elements/vault/config.mdx +256 -0
  57. package/site/content/docs/elements/vault/index.mdx +356 -0
  58. package/site/content/docs/elements/vault/meta.json +5 -0
  59. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  60. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  61. package/site/content/docs/index.mdx +63 -36
  62. package/site/content/docs/meta.json +8 -7
  63. package/site/content/docs/plugins/cors.mdx +2 -1
  64. package/site/content/docs/plugins/csrf.mdx +4 -1
  65. package/site/content/docs/plugins/headers.mdx +2 -1
  66. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  67. package/site/content/docs/plugins/magic-link.mdx +6 -0
  68. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  69. package/site/content/docs/plugins/oauth.mdx +1 -1
  70. package/site/content/docs/plugins/otp.mdx +12 -1
  71. package/site/content/docs/plugins/passkey.mdx +39 -15
  72. package/site/content/docs/plugins/two-factor.mdx +91 -34
  73. package/site/content/docs/plugins/username.mdx +1 -1
  74. package/site/content/docs/recipes/caddy.mdx +3 -3
  75. package/site/content/docs/recipes/index.mdx +18 -28
  76. package/site/content/docs/recipes/mailpit.mdx +1 -1
  77. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  78. package/site/content/docs/recipes/meta.json +1 -4
  79. package/site/content/docs/recipes/nginx.mdx +2 -2
  80. package/site/content/docs/recipes/openrouter.mdx +283 -0
  81. package/site/content/docs/recipes/pgdog.mdx +3 -3
  82. package/site/content/docs/recipes/postgres.mdx +1 -2
  83. package/site/content/docs/recipes/rustfs.mdx +2 -2
  84. package/site/content/docs/recipes/traefik.mdx +3 -3
  85. package/site/content/docs/reference/cli.mdx +231 -0
  86. package/site/content/docs/reference/configuration.mdx +30 -10
  87. package/site/content/docs/reference/environment-variables.mdx +93 -20
  88. package/site/content/docs/reference/errors.mdx +132 -29
  89. package/site/content/docs/reference/fx.mdx +137 -73
  90. package/site/content/docs/reference/index.mdx +13 -21
  91. package/site/content/docs/reference/meta.json +4 -5
  92. package/site/content/docs/reference/okid.mdx +42 -17
  93. package/site/content/docs/reference/plugins.mdx +4 -3
  94. package/site/content/docs/reference/security.mdx +197 -0
  95. package/site/content/docs/understand/meta.json +5 -0
  96. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  97. package/site/content/docs/understand/the-model.mdx +32 -0
  98. package/site/content/docs/understand/the-problem.mdx +74 -0
  99. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  100. package/src/auth/api-keys.ts +2 -1
  101. package/src/auth/bindings.ts +64 -16
  102. package/src/auth/gate-auth.test.ts +6 -1
  103. package/src/auth/identity.ts +152 -5
  104. package/src/auth/index.ts +29 -0
  105. package/src/auth/invites.ts +2 -1
  106. package/src/auth/method-context.ts +41 -0
  107. package/src/auth/oauth-as/crypto.ts +2 -1
  108. package/src/auth/oauth-as/stores.ts +2 -1
  109. package/src/auth/operator.ts +2 -1
  110. package/src/auth/sessions-jwt.test.ts +77 -0
  111. package/src/auth/sessions.ts +20 -4
  112. package/src/auth/tenants.ts +3 -2
  113. package/src/auth/two-factor-challenge.test.ts +61 -0
  114. package/src/auth/two-factor-challenge.ts +284 -0
  115. package/src/auth/verification.ts +5 -0
  116. package/src/bench/REPORT.md +49 -0
  117. package/src/bench/g03-signal-once.bench.ts +1 -1
  118. package/src/bench/g10-observability-contention.bench.ts +1 -1
  119. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  120. package/src/bench/load-app.ts +2 -10
  121. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  122. package/src/cli/ai-setup/apply.ts +294 -52
  123. package/src/cli/ai-setup/catalog.ts +238 -1342
  124. package/src/cli/ai-setup/index.ts +14 -99
  125. package/src/cli/ai-setup/prompts.ts +60 -443
  126. package/src/cli/ask-seed.test.ts +96 -0
  127. package/src/cli/ask-seed.ts +82 -0
  128. package/src/cli/ask-vault-gaps.test.ts +84 -0
  129. package/src/cli/ask-vault-gaps.ts +146 -0
  130. package/src/cli/build.ts +2 -2
  131. package/src/cli/client-add.test.ts +26 -1
  132. package/src/cli/client-add.ts +106 -27
  133. package/src/cli/db-seed.ts +2 -0
  134. package/src/cli/db.ts +150 -11
  135. package/src/cli/dev.ts +119 -211
  136. package/src/cli/docker-clean.ts +2 -2
  137. package/src/cli/docker-cli.test.ts +1 -1
  138. package/src/cli/doctor-diff.ts +4 -2
  139. package/src/cli/doctor-pii.test.ts +1 -1
  140. package/src/cli/doctor.ts +14 -1
  141. package/src/cli/hero-meta.test.ts +4 -2
  142. package/src/cli/load-config.images.test.ts +8 -10
  143. package/src/cli/load-config.ts +1 -1
  144. package/src/cli/registry.ts +22 -10
  145. package/src/cli/replay.ts +3 -1
  146. package/src/cli/start.ts +1 -1
  147. package/src/cli/tui/keys.ts +2 -3
  148. package/src/client/auth/cookies.ts +74 -0
  149. package/src/client/auth/create-auth-client.ts +636 -0
  150. package/src/client/auth/denials.ts +99 -0
  151. package/src/client/auth/session.ts +283 -0
  152. package/src/client/auth.test.ts +251 -0
  153. package/src/client/auth.ts +39 -114
  154. package/src/client/create-with-session.ts +245 -0
  155. package/src/client/create.ts +51 -5
  156. package/src/client/index.ts +11 -1
  157. package/src/client/live.ts +11 -79
  158. package/src/client/notes-contract.test.ts +5 -10
  159. package/src/client/sse.ts +137 -0
  160. package/src/client/stream.ts +147 -0
  161. package/src/client/transport.test.ts +32 -0
  162. package/src/client/transport.ts +123 -26
  163. package/src/client/types.ts +118 -14
  164. package/src/client-react/index.ts +185 -24
  165. package/src/client-react/use-live-query.ts +13 -3
  166. package/src/compiler/aot.test.ts +7 -4
  167. package/src/compiler/effects-embed.test.ts +56 -0
  168. package/src/compiler/effects-fetch.test.ts +43 -0
  169. package/src/compiler/effects-infer.ts +58 -2
  170. package/src/compiler/extract.test.ts +257 -43
  171. package/src/compiler/extract.ts +566 -48
  172. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  173. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  174. package/src/compiler/fixtures/skyport.expected.json +4 -4
  175. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  176. package/src/compiler/response.ts +2 -2
  177. package/src/compiler/search-writer-isolation.test.ts +40 -0
  178. package/src/config/index.ts +11 -0
  179. package/src/console/server/app.ts +17 -23
  180. package/src/console/server/bind.ts +4 -0
  181. package/src/console/server/console.test.ts +2 -2
  182. package/src/console/server/flows-invoke.test.ts +50 -34
  183. package/src/console/server/flows.ts +436 -255
  184. package/src/console/server/runs-ingest.test.ts +6 -3
  185. package/src/console/server/serve.ts +1 -1
  186. package/src/console/server/signals.test.ts +1 -5
  187. package/src/console/server/signals.ts +8 -6
  188. package/src/console/server/state.ts +6 -1
  189. package/src/console/server/store.ts +1 -1
  190. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  191. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  192. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  193. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  194. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  195. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  196. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  197. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  198. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  199. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  200. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  201. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  202. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  203. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  204. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  205. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  206. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  207. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  208. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  209. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  210. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  211. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  212. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  213. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  214. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  215. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  216. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  217. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  218. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  219. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  220. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  221. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  222. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  223. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  224. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  225. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  226. package/src/console/ui-next/dist/index.html +6 -13
  227. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  228. package/src/console/ui-next/src/client.ts +7 -2
  229. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  230. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  231. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  232. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  233. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  235. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  236. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  237. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  238. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  239. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  240. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  241. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  242. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  243. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  244. package/src/docker/ai-model-status.test.ts +1 -12
  245. package/src/docker/ai-model-status.ts +10 -38
  246. package/src/docker/compose-up.test.ts +100 -0
  247. package/src/docker/compose-up.ts +292 -0
  248. package/src/docker/compose.ts +4 -14
  249. package/src/docker/derive.ts +9 -34
  250. package/src/docker/docker.test.ts +16 -261
  251. package/src/docker/images-config.test.ts +13 -25
  252. package/src/docker/index.ts +1 -24
  253. package/src/docker/recipes/index.ts +0 -23
  254. package/src/docker/recipes/pgdog.ts +6 -4
  255. package/src/docker/stack-id.test.ts +2 -2
  256. package/src/docker/stack-id.ts +0 -1
  257. package/src/docker/types.ts +2 -2
  258. package/src/drivers/ai-anthropic.ts +10 -0
  259. package/src/drivers/ai-openai-compatible.ts +34 -7
  260. package/src/drivers/ai-providers.test.ts +0 -107
  261. package/src/drivers/ai-stream.test.ts +0 -34
  262. package/src/drivers/ai-types.ts +17 -8
  263. package/src/drivers/channel-fcm.ts +6 -1
  264. package/src/drivers/channel-msegat.ts +6 -1
  265. package/src/drivers/channel-resend.ts +5 -1
  266. package/src/drivers/channel-smtp.ts +5 -1
  267. package/src/drivers/channel-sndr.ts +10 -1
  268. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  269. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  270. package/src/drivers/channel-taqnyat.ts +6 -1
  271. package/src/drivers/channel-types.ts +10 -0
  272. package/src/drivers/channel-unifonic.ts +6 -1
  273. package/src/drivers/channel-wa-cloud.ts +6 -1
  274. package/src/drivers/channel-webpush.ts +6 -1
  275. package/src/drivers/external.ts +33 -0
  276. package/src/drivers/index.ts +0 -10
  277. package/src/drivers/meilisearch.ts +6 -0
  278. package/src/drivers/oauth-types.ts +4 -0
  279. package/src/drivers/postgres.test.ts +74 -3
  280. package/src/drivers/postgres.ts +175 -6
  281. package/src/drivers/signal-types.ts +5 -5
  282. package/src/drivers/types.ts +7 -0
  283. package/src/drivers/vault-types.ts +5 -0
  284. package/src/elements/ai/declare.ts +17 -4
  285. package/src/elements/ai/eval.ts +1 -1
  286. package/src/elements/ai/mcp-http.ts +1 -1
  287. package/src/elements/ai/pii.ts +1 -1
  288. package/src/elements/ai/providers.test.ts +289 -0
  289. package/src/elements/ai/providers.ts +169 -0
  290. package/src/elements/ai/runtime.ts +68 -5
  291. package/src/elements/ai/schema.ts +2 -2
  292. package/src/elements/ai.test.ts +7 -7
  293. package/src/elements/ai.ts +17 -1
  294. package/src/elements/channel/runtime.ts +105 -12
  295. package/src/elements/clock/cron-fields.test.ts +144 -0
  296. package/src/elements/clock/cron-fields.ts +185 -0
  297. package/src/elements/clock/declare.ts +247 -3
  298. package/src/elements/clock.ts +21 -1
  299. package/src/elements/index.ts +15 -0
  300. package/src/elements/signal/chaos-child.ts +1 -2
  301. package/src/elements/signal/declare.ts +65 -37
  302. package/src/elements/signal/delivery-modes.test.ts +11 -31
  303. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  304. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  305. package/src/elements/signal/key-ordering.test.ts +6 -26
  306. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  307. package/src/elements/signal/optional-emit.test.ts +6 -9
  308. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  309. package/src/elements/signal/orphan-messages.test.ts +4 -15
  310. package/src/elements/signal/reconcile.test.ts +2 -2
  311. package/src/elements/signal/runtime.ts +1 -1
  312. package/src/elements/signal/schema-emit.test.ts +4 -8
  313. package/src/elements/signal.test.ts +24 -49
  314. package/src/elements/signal.ts +9 -2
  315. package/src/elements/store/domain-ddl.test.ts +1 -1
  316. package/src/elements/store/live-http.test.ts +9 -4
  317. package/src/elements/store/prepare-row.test.ts +123 -0
  318. package/src/elements/store/resource.ts +43 -12
  319. package/src/elements/store/schema-decl.ts +117 -0
  320. package/src/elements/store/search-backfill.ts +205 -0
  321. package/src/elements/store/search-bind.ts +66 -0
  322. package/src/elements/store/search-bm25.ts +78 -0
  323. package/src/elements/store/search-ddl.ts +156 -0
  324. package/src/elements/store/search-embed-flow.ts +141 -0
  325. package/src/elements/store/search-errors.ts +34 -0
  326. package/src/elements/store/search-fusion.ts +112 -0
  327. package/src/elements/store/search-lsh.ts +179 -0
  328. package/src/elements/store/search-runtime.ts +300 -0
  329. package/src/elements/store/search.test.ts +144 -0
  330. package/src/elements/store/sql-session.test.ts +1 -0
  331. package/src/elements/store/sql-session.ts +83 -3
  332. package/src/elements/store/table.ts +23 -2
  333. package/src/elements/store/upsert-app.test.ts +1 -3
  334. package/src/elements/store.ts +50 -0
  335. package/src/full.ts +4 -2
  336. package/src/http.ts +4 -1
  337. package/src/i18n/catalogs/ar.ts +14 -6
  338. package/src/i18n/catalogs/en.ts +14 -6
  339. package/src/index.ts +6 -1
  340. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  341. package/src/kernel/adopt-routes.ts +9 -1
  342. package/src/kernel/app-auth.ts +5 -0
  343. package/src/kernel/app.ts +102 -9
  344. package/src/kernel/auto-cache.test.ts +6 -12
  345. package/src/kernel/auto-registry.test.ts +3 -3
  346. package/src/kernel/boot-bind/ai.test.ts +22 -19
  347. package/src/kernel/boot-bind/ai.ts +5 -43
  348. package/src/kernel/boot-bind/clock.ts +23 -5
  349. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  350. package/src/kernel/boot.test.ts +11 -6
  351. package/src/kernel/boot.ts +66 -16
  352. package/src/kernel/boundary-contract.ts +93 -0
  353. package/src/kernel/budget.test.ts +1 -1
  354. package/src/kernel/call.ts +89 -0
  355. package/src/kernel/capability.ts +6 -0
  356. package/src/kernel/client-descriptor.ts +123 -0
  357. package/src/kernel/clock-timezone.test.ts +80 -0
  358. package/src/kernel/correlation.test.ts +1 -1
  359. package/src/kernel/dry-run.ts +11 -8
  360. package/src/kernel/effects-stamping.test.ts +42 -9
  361. package/src/kernel/effects.test.ts +4 -2
  362. package/src/kernel/effects.ts +51 -9
  363. package/src/kernel/errors-channel.ts +17 -0
  364. package/src/kernel/errors-live-resume.ts +3 -2
  365. package/src/kernel/errors-tenant.ts +7 -4
  366. package/src/kernel/errors.registry-helpers.ts +96 -0
  367. package/src/kernel/errors.registry.test.ts +139 -25
  368. package/src/kernel/errors.ts +102 -23
  369. package/src/kernel/external-effects.test.ts +196 -0
  370. package/src/kernel/flow.test.ts +13 -4
  371. package/src/kernel/flow.ts +69 -71
  372. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  373. package/src/kernel/fx-dead-letters.test.ts +3 -3
  374. package/src/kernel/fx-fetch.ts +55 -0
  375. package/src/kernel/fx-live-stream.ts +2 -2
  376. package/src/kernel/fx-live.test.ts +8 -8
  377. package/src/kernel/fx.test.ts +17 -11
  378. package/src/kernel/fx.ts +180 -57
  379. package/src/kernel/horizontal-child.ts +1 -1
  380. package/src/kernel/http-query.test.ts +3 -6
  381. package/src/kernel/index.ts +13 -3
  382. package/src/kernel/instance-id.ts +1 -1
  383. package/src/kernel/instances.test.ts +40 -0
  384. package/src/kernel/instances.ts +47 -9
  385. package/src/kernel/live-http.test.ts +6 -7
  386. package/src/kernel/live-http.ts +6 -2
  387. package/src/kernel/live-resume.test.ts +1 -1
  388. package/src/kernel/mcp-tool.test.ts +3 -3
  389. package/src/kernel/on.ts +98 -4
  390. package/src/kernel/pipeline.test.ts +12 -15
  391. package/src/kernel/plugin-elements.test.ts +1 -1
  392. package/src/kernel/stamp-http.test.ts +3 -3
  393. package/src/kernel/triggers.ts +210 -95
  394. package/src/kernel-entry.ts +0 -1
  395. package/src/manifest/diff.ts +11 -1
  396. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  397. package/src/manifest/types.ts +46 -2
  398. package/src/mcp/docs-index.ts +3 -3
  399. package/src/mcp/docs-mcp.test.ts +2 -2
  400. package/src/mcp/docs-tools.ts +1 -1
  401. package/src/okid.test.ts +56 -0
  402. package/src/okid.ts +55 -11
  403. package/src/plugins/anonymous.ts +8 -4
  404. package/src/plugins/auth/shared.ts +27 -3
  405. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  406. package/src/plugins/auth-methods.security.test.ts +561 -23
  407. package/src/plugins/config-source.ts +1 -1
  408. package/src/plugins/csrf.test.ts +65 -0
  409. package/src/plugins/index.ts +2 -0
  410. package/src/plugins/magic-link.ts +36 -16
  411. package/src/plugins/oauth/shared.ts +3 -1
  412. package/src/plugins/oauth.ts +22 -14
  413. package/src/plugins/otp.ts +37 -21
  414. package/src/plugins/passkey-webauthn.ts +9 -6
  415. package/src/plugins/passkey.ts +98 -26
  416. package/src/plugins/pre-account-hijack.test.ts +223 -0
  417. package/src/plugins/two-factor.ts +466 -37
  418. package/src/plugins/username.ts +19 -7
  419. package/src/release/build-lib.ts +1 -0
  420. package/src/release/limits.ts +2 -2
  421. package/src/release/measure.ts +1 -1
  422. package/src/runs/collect.test.ts +0 -2
  423. package/src/runtime/json-code-block.test.ts +250 -20
  424. package/src/runtime/json-code-block.ts +1261 -41
  425. package/src/term.test.ts +1 -1
  426. package/src/test/create-test-app.test.ts +12 -12
  427. package/src/test/live-signals.test.ts +6 -6
  428. package/src/test/provisions.integration.test.ts +10 -14
  429. package/src/test/reset-element-registries.ts +1 -1
  430. package/src/test/tenant-isolation.test.ts +12 -6
  431. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  432. package/site/content/docs/deployment/docker.mdx +0 -227
  433. package/site/content/docs/deployment/index.mdx +0 -83
  434. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  435. package/site/content/docs/deployment/meta.json +0 -5
  436. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  437. package/site/content/docs/elements/ai.mdx +0 -385
  438. package/site/content/docs/elements/channel.mdx +0 -346
  439. package/site/content/docs/elements/clock.mdx +0 -244
  440. package/site/content/docs/elements/flow.mdx +0 -420
  441. package/site/content/docs/elements/gate.mdx +0 -436
  442. package/site/content/docs/elements/signal.mdx +0 -380
  443. package/site/content/docs/elements/store.mdx +0 -1099
  444. package/site/content/docs/elements/vault.mdx +0 -405
  445. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  446. package/site/content/docs/get-started/index.mdx +0 -43
  447. package/site/content/docs/get-started/installation.mdx +0 -220
  448. package/site/content/docs/get-started/introduction.mdx +0 -144
  449. package/site/content/docs/get-started/meta.json +0 -13
  450. package/site/content/docs/get-started/project-structure.mdx +0 -925
  451. package/site/content/docs/get-started/testing.mdx +0 -328
  452. package/site/content/docs/get-started/why.mdx +0 -114
  453. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  454. package/site/content/docs/recipes/ollama.mdx +0 -142
  455. package/site/content/docs/recipes/sglang.mdx +0 -105
  456. package/site/content/docs/recipes/vllm.mdx +0 -106
  457. package/site/content/docs/reference/cli.md +0 -232
  458. package/site/content/docs/reference/client.mdx +0 -469
  459. package/site/content/docs/reference/security.md +0 -74
  460. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  461. package/src/cli/ai-setup/recommend.test.ts +0 -225
  462. package/src/cli/ai-setup/recommend.ts +0 -225
  463. package/src/cli/dev-controls.test.ts +0 -122
  464. package/src/cli/dev-controls.ts +0 -164
  465. package/src/cli/tui/DevLive.tsx +0 -126
  466. package/src/console/ui-next/dist/assets/access-page-3-EFj-2G.js +0 -4
  467. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  468. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  469. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  470. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  471. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  472. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  473. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  474. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  475. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  476. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  477. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  478. package/src/console/ui-next/dist/assets/flows-page-cVFnA4HH.js +0 -1
  479. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  480. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  481. package/src/console/ui-next/dist/assets/index-CMIgUbD0.js +0 -66
  482. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  483. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  484. package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.js +0 -4
  485. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  486. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  487. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  488. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  489. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  490. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  491. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  492. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  493. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  494. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  495. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  496. package/src/console/ui-next/dist/assets/units-page-l8FeKfnP.js +0 -1
  497. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  498. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  499. package/src/console/ui-next/dist/assets/vault-page-CL-d_mLE.js +0 -2
  500. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  501. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  502. package/src/docker/ollama-pull.ts +0 -232
  503. package/src/docker/recipes/llama-cpp.ts +0 -298
  504. package/src/docker/recipes/ollama.ts +0 -44
  505. package/src/docker/recipes/sglang.ts +0 -55
  506. package/src/docker/recipes/vllm.ts +0 -44
  507. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  508. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  509. package/src/drivers/ai-ollama.ts +0 -389
  510. 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";