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.
- package/AGENTS.md +18 -10
- package/README.md +2 -2
- package/package.json +51 -50
- package/site/content/docs/ai/llms-txt.mdx +1 -1
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/ai/skills.mdx +4 -1
- package/site/content/docs/ai/try-it.mdx +58 -0
- package/site/content/docs/client/auth.mdx +208 -0
- package/site/content/docs/client/calling.mdx +451 -0
- package/site/content/docs/client/index.mdx +209 -0
- package/site/content/docs/client/live.mdx +234 -0
- package/site/content/docs/client/meta.json +5 -0
- package/site/content/docs/client/react.mdx +249 -0
- package/site/content/docs/elements/ai/agents.mdx +232 -0
- package/site/content/docs/elements/ai/index.mdx +341 -0
- package/site/content/docs/elements/ai/mcp.mdx +275 -0
- package/site/content/docs/elements/ai/meta.json +5 -0
- package/site/content/docs/elements/ai/models.mdx +283 -0
- package/site/content/docs/elements/ai/prompts.mdx +256 -0
- package/site/content/docs/elements/channel/email.mdx +266 -0
- package/site/content/docs/elements/channel/index.mdx +344 -0
- package/site/content/docs/elements/channel/meta.json +5 -0
- package/site/content/docs/elements/channel/push.mdx +218 -0
- package/site/content/docs/elements/channel/receipts.mdx +206 -0
- package/site/content/docs/elements/channel/sms.mdx +264 -0
- package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
- package/site/content/docs/elements/clock/index.mdx +300 -0
- package/site/content/docs/elements/clock/meta.json +5 -0
- package/site/content/docs/elements/clock/schedules.mdx +508 -0
- package/site/content/docs/elements/clock/sleep.mdx +445 -0
- package/site/content/docs/elements/flow/consumers.mdx +707 -0
- package/site/content/docs/elements/flow/http.mdx +1080 -0
- package/site/content/docs/elements/flow/index.mdx +618 -0
- package/site/content/docs/elements/flow/meta.json +5 -0
- package/site/content/docs/elements/flow/routing.mdx +559 -0
- package/site/content/docs/elements/flow/workflows.mdx +640 -0
- package/site/content/docs/elements/gate/auth.mdx +371 -0
- package/site/content/docs/elements/gate/authorization.mdx +298 -0
- package/site/content/docs/elements/gate/index.mdx +418 -0
- package/site/content/docs/elements/gate/meta.json +5 -0
- package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
- package/site/content/docs/elements/gate/rls.mdx +303 -0
- package/site/content/docs/elements/gate/tenancy.mdx +372 -0
- package/site/content/docs/elements/index.mdx +20 -10
- package/site/content/docs/elements/signal/broadcast.mdx +517 -0
- package/site/content/docs/elements/signal/index.mdx +352 -0
- package/site/content/docs/elements/signal/live.mdx +590 -0
- package/site/content/docs/elements/signal/meta.json +5 -0
- package/site/content/docs/elements/signal/once.mdx +596 -0
- package/site/content/docs/elements/store/files.mdx +659 -0
- package/site/content/docs/elements/store/index.mdx +310 -0
- package/site/content/docs/elements/store/kv.mdx +629 -0
- package/site/content/docs/elements/store/meta.json +5 -0
- package/site/content/docs/elements/store/search.mdx +946 -0
- package/site/content/docs/elements/store/sql.mdx +937 -0
- package/site/content/docs/elements/vault/config.mdx +256 -0
- package/site/content/docs/elements/vault/index.mdx +356 -0
- package/site/content/docs/elements/vault/meta.json +5 -0
- package/site/content/docs/elements/vault/rotation.mdx +273 -0
- package/site/content/docs/elements/vault/secrets.mdx +285 -0
- package/site/content/docs/index.mdx +63 -36
- package/site/content/docs/meta.json +8 -7
- package/site/content/docs/plugins/cors.mdx +2 -1
- package/site/content/docs/plugins/csrf.mdx +4 -1
- package/site/content/docs/plugins/headers.mdx +2 -1
- package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
- package/site/content/docs/plugins/magic-link.mdx +6 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
- package/site/content/docs/plugins/oauth.mdx +1 -1
- package/site/content/docs/plugins/otp.mdx +12 -1
- package/site/content/docs/plugins/passkey.mdx +39 -15
- package/site/content/docs/plugins/two-factor.mdx +91 -34
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +3 -3
- package/site/content/docs/recipes/index.mdx +18 -28
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +3 -3
- package/site/content/docs/recipes/meta.json +1 -4
- package/site/content/docs/recipes/nginx.mdx +2 -2
- package/site/content/docs/recipes/openrouter.mdx +283 -0
- package/site/content/docs/recipes/pgdog.mdx +3 -3
- package/site/content/docs/recipes/postgres.mdx +1 -2
- package/site/content/docs/recipes/rustfs.mdx +2 -2
- package/site/content/docs/recipes/traefik.mdx +3 -3
- package/site/content/docs/reference/cli.mdx +231 -0
- package/site/content/docs/reference/configuration.mdx +30 -10
- package/site/content/docs/reference/environment-variables.mdx +93 -20
- package/site/content/docs/reference/errors.mdx +132 -29
- package/site/content/docs/reference/fx.mdx +137 -73
- package/site/content/docs/reference/index.mdx +13 -21
- package/site/content/docs/reference/meta.json +4 -5
- package/site/content/docs/reference/okid.mdx +42 -17
- package/site/content/docs/reference/plugins.mdx +4 -3
- package/site/content/docs/reference/security.mdx +197 -0
- package/site/content/docs/understand/meta.json +5 -0
- package/site/content/docs/understand/the-anatomy.mdx +132 -0
- package/site/content/docs/understand/the-model.mdx +32 -0
- package/site/content/docs/understand/the-problem.mdx +74 -0
- package/site/content/docs/understand/the-vocabulary.mdx +26 -0
- package/src/auth/api-keys.ts +2 -1
- package/src/auth/bindings.ts +64 -16
- package/src/auth/gate-auth.test.ts +6 -1
- package/src/auth/identity.ts +152 -5
- package/src/auth/index.ts +29 -0
- package/src/auth/invites.ts +2 -1
- package/src/auth/method-context.ts +41 -0
- package/src/auth/oauth-as/crypto.ts +2 -1
- package/src/auth/oauth-as/stores.ts +2 -1
- package/src/auth/operator.ts +2 -1
- package/src/auth/sessions-jwt.test.ts +77 -0
- package/src/auth/sessions.ts +20 -4
- package/src/auth/tenants.ts +3 -2
- package/src/auth/two-factor-challenge.test.ts +61 -0
- package/src/auth/two-factor-challenge.ts +284 -0
- package/src/auth/verification.ts +5 -0
- package/src/bench/REPORT.md +49 -0
- package/src/bench/g03-signal-once.bench.ts +1 -1
- package/src/bench/g10-observability-contention.bench.ts +1 -1
- package/src/bench/g17-hybrid-search.bench.ts +572 -0
- package/src/bench/load-app.ts +2 -10
- package/src/cli/ai-setup/ai-setup.test.ts +331 -42
- package/src/cli/ai-setup/apply.ts +294 -52
- package/src/cli/ai-setup/catalog.ts +238 -1342
- package/src/cli/ai-setup/index.ts +14 -99
- package/src/cli/ai-setup/prompts.ts +60 -443
- package/src/cli/ask-seed.test.ts +96 -0
- package/src/cli/ask-seed.ts +82 -0
- package/src/cli/ask-vault-gaps.test.ts +84 -0
- package/src/cli/ask-vault-gaps.ts +146 -0
- package/src/cli/build.ts +2 -2
- package/src/cli/client-add.test.ts +26 -1
- package/src/cli/client-add.ts +106 -27
- package/src/cli/db-seed.ts +2 -0
- package/src/cli/db.ts +150 -11
- package/src/cli/dev.ts +119 -211
- package/src/cli/docker-clean.ts +2 -2
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/doctor-diff.ts +4 -2
- package/src/cli/doctor-pii.test.ts +1 -1
- package/src/cli/doctor.ts +14 -1
- package/src/cli/hero-meta.test.ts +4 -2
- package/src/cli/load-config.images.test.ts +8 -10
- package/src/cli/load-config.ts +1 -1
- package/src/cli/registry.ts +22 -10
- package/src/cli/replay.ts +3 -1
- package/src/cli/start.ts +1 -1
- package/src/cli/tui/keys.ts +2 -3
- package/src/client/auth/cookies.ts +74 -0
- package/src/client/auth/create-auth-client.ts +636 -0
- package/src/client/auth/denials.ts +99 -0
- package/src/client/auth/session.ts +283 -0
- package/src/client/auth.test.ts +251 -0
- package/src/client/auth.ts +39 -114
- package/src/client/create-with-session.ts +245 -0
- package/src/client/create.ts +51 -5
- package/src/client/index.ts +11 -1
- package/src/client/live.ts +11 -79
- package/src/client/notes-contract.test.ts +5 -10
- package/src/client/sse.ts +137 -0
- package/src/client/stream.ts +147 -0
- package/src/client/transport.test.ts +32 -0
- package/src/client/transport.ts +123 -26
- package/src/client/types.ts +118 -14
- package/src/client-react/index.ts +185 -24
- package/src/client-react/use-live-query.ts +13 -3
- package/src/compiler/aot.test.ts +7 -4
- package/src/compiler/effects-embed.test.ts +56 -0
- package/src/compiler/effects-fetch.test.ts +43 -0
- package/src/compiler/effects-infer.ts +58 -2
- package/src/compiler/extract.test.ts +257 -43
- package/src/compiler/extract.ts +566 -48
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
- package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
- package/src/compiler/fixtures/skyport.expected.json +4 -4
- package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
- package/src/compiler/response.ts +2 -2
- package/src/compiler/search-writer-isolation.test.ts +40 -0
- package/src/config/index.ts +11 -0
- package/src/console/server/app.ts +17 -23
- package/src/console/server/bind.ts +4 -0
- package/src/console/server/console.test.ts +2 -2
- package/src/console/server/flows-invoke.test.ts +50 -34
- package/src/console/server/flows.ts +436 -255
- package/src/console/server/runs-ingest.test.ts +6 -3
- package/src/console/server/serve.ts +1 -1
- package/src/console/server/signals.test.ts +1 -5
- package/src/console/server/signals.ts +8 -6
- package/src/console/server/state.ts +6 -1
- package/src/console/server/store.ts +1 -1
- package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
- package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
- package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
- package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
- package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
- package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
- package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
- package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
- package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
- package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
- package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
- package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
- package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
- package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
- package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
- package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
- package/src/console/ui-next/dist/index.html +6 -13
- package/src/console/ui-next/seed-invoke-host.ts +21 -18
- package/src/console/ui-next/src/client.ts +7 -2
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
- package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
- package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
- package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
- package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
- package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
- package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
- package/src/docker/ai-model-status.test.ts +1 -12
- package/src/docker/ai-model-status.ts +10 -38
- package/src/docker/compose-up.test.ts +100 -0
- package/src/docker/compose-up.ts +292 -0
- package/src/docker/compose.ts +4 -14
- package/src/docker/derive.ts +9 -34
- package/src/docker/docker.test.ts +16 -261
- package/src/docker/images-config.test.ts +13 -25
- package/src/docker/index.ts +1 -24
- package/src/docker/recipes/index.ts +0 -23
- package/src/docker/recipes/pgdog.ts +6 -4
- package/src/docker/stack-id.test.ts +2 -2
- package/src/docker/stack-id.ts +0 -1
- package/src/docker/types.ts +2 -2
- package/src/drivers/ai-anthropic.ts +10 -0
- package/src/drivers/ai-openai-compatible.ts +34 -7
- package/src/drivers/ai-providers.test.ts +0 -107
- package/src/drivers/ai-stream.test.ts +0 -34
- package/src/drivers/ai-types.ts +17 -8
- package/src/drivers/channel-fcm.ts +6 -1
- package/src/drivers/channel-msegat.ts +6 -1
- package/src/drivers/channel-resend.ts +5 -1
- package/src/drivers/channel-smtp.ts +5 -1
- package/src/drivers/channel-sndr.ts +10 -1
- package/src/drivers/channel-taqnyat-mail.ts +5 -1
- package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
- package/src/drivers/channel-taqnyat.ts +6 -1
- package/src/drivers/channel-types.ts +10 -0
- package/src/drivers/channel-unifonic.ts +6 -1
- package/src/drivers/channel-wa-cloud.ts +6 -1
- package/src/drivers/channel-webpush.ts +6 -1
- package/src/drivers/external.ts +33 -0
- package/src/drivers/index.ts +0 -10
- package/src/drivers/meilisearch.ts +6 -0
- package/src/drivers/oauth-types.ts +4 -0
- package/src/drivers/postgres.test.ts +74 -3
- package/src/drivers/postgres.ts +175 -6
- package/src/drivers/signal-types.ts +5 -5
- package/src/drivers/types.ts +7 -0
- package/src/drivers/vault-types.ts +5 -0
- package/src/elements/ai/declare.ts +17 -4
- package/src/elements/ai/eval.ts +1 -1
- package/src/elements/ai/mcp-http.ts +1 -1
- package/src/elements/ai/pii.ts +1 -1
- package/src/elements/ai/providers.test.ts +289 -0
- package/src/elements/ai/providers.ts +169 -0
- package/src/elements/ai/runtime.ts +68 -5
- package/src/elements/ai/schema.ts +2 -2
- package/src/elements/ai.test.ts +7 -7
- package/src/elements/ai.ts +17 -1
- package/src/elements/channel/runtime.ts +105 -12
- package/src/elements/clock/cron-fields.test.ts +144 -0
- package/src/elements/clock/cron-fields.ts +185 -0
- package/src/elements/clock/declare.ts +247 -3
- package/src/elements/clock.ts +21 -1
- package/src/elements/index.ts +15 -0
- package/src/elements/signal/chaos-child.ts +1 -2
- package/src/elements/signal/declare.ts +65 -37
- package/src/elements/signal/delivery-modes.test.ts +11 -31
- package/src/elements/signal/dry-run-replay.test.ts +1 -7
- package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
- package/src/elements/signal/key-ordering.test.ts +6 -26
- package/src/elements/signal/lease-reclaim.test.ts +2 -6
- package/src/elements/signal/optional-emit.test.ts +6 -9
- package/src/elements/signal/order-lifecycle.test.ts +4 -17
- package/src/elements/signal/orphan-messages.test.ts +4 -15
- package/src/elements/signal/reconcile.test.ts +2 -2
- package/src/elements/signal/runtime.ts +1 -1
- package/src/elements/signal/schema-emit.test.ts +4 -8
- package/src/elements/signal.test.ts +24 -49
- package/src/elements/signal.ts +9 -2
- package/src/elements/store/domain-ddl.test.ts +1 -1
- package/src/elements/store/live-http.test.ts +9 -4
- package/src/elements/store/prepare-row.test.ts +123 -0
- package/src/elements/store/resource.ts +43 -12
- package/src/elements/store/schema-decl.ts +117 -0
- package/src/elements/store/search-backfill.ts +205 -0
- package/src/elements/store/search-bind.ts +66 -0
- package/src/elements/store/search-bm25.ts +78 -0
- package/src/elements/store/search-ddl.ts +156 -0
- package/src/elements/store/search-embed-flow.ts +141 -0
- package/src/elements/store/search-errors.ts +34 -0
- package/src/elements/store/search-fusion.ts +112 -0
- package/src/elements/store/search-lsh.ts +179 -0
- package/src/elements/store/search-runtime.ts +300 -0
- package/src/elements/store/search.test.ts +144 -0
- package/src/elements/store/sql-session.test.ts +1 -0
- package/src/elements/store/sql-session.ts +83 -3
- package/src/elements/store/table.ts +23 -2
- package/src/elements/store/upsert-app.test.ts +1 -3
- package/src/elements/store.ts +50 -0
- package/src/full.ts +4 -2
- package/src/http.ts +4 -1
- package/src/i18n/catalogs/ar.ts +14 -6
- package/src/i18n/catalogs/en.ts +14 -6
- package/src/index.ts +6 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
- package/src/kernel/adopt-routes.ts +9 -1
- package/src/kernel/app-auth.ts +5 -0
- package/src/kernel/app.ts +102 -9
- package/src/kernel/auto-cache.test.ts +6 -12
- package/src/kernel/auto-registry.test.ts +3 -3
- package/src/kernel/boot-bind/ai.test.ts +22 -19
- package/src/kernel/boot-bind/ai.ts +5 -43
- package/src/kernel/boot-bind/clock.ts +23 -5
- package/src/kernel/boot-bind/honor-config.test.ts +4 -4
- package/src/kernel/boot.test.ts +11 -6
- package/src/kernel/boot.ts +66 -16
- package/src/kernel/boundary-contract.ts +93 -0
- package/src/kernel/budget.test.ts +1 -1
- package/src/kernel/call.ts +89 -0
- package/src/kernel/capability.ts +6 -0
- package/src/kernel/client-descriptor.ts +123 -0
- package/src/kernel/clock-timezone.test.ts +80 -0
- package/src/kernel/correlation.test.ts +1 -1
- package/src/kernel/dry-run.ts +11 -8
- package/src/kernel/effects-stamping.test.ts +42 -9
- package/src/kernel/effects.test.ts +4 -2
- package/src/kernel/effects.ts +51 -9
- package/src/kernel/errors-channel.ts +17 -0
- package/src/kernel/errors-live-resume.ts +3 -2
- package/src/kernel/errors-tenant.ts +7 -4
- package/src/kernel/errors.registry-helpers.ts +96 -0
- package/src/kernel/errors.registry.test.ts +139 -25
- package/src/kernel/errors.ts +102 -23
- package/src/kernel/external-effects.test.ts +196 -0
- package/src/kernel/flow.test.ts +13 -4
- package/src/kernel/flow.ts +69 -71
- package/src/kernel/fx-ask-telemetry.test.ts +2 -2
- package/src/kernel/fx-dead-letters.test.ts +3 -3
- package/src/kernel/fx-fetch.ts +55 -0
- package/src/kernel/fx-live-stream.ts +2 -2
- package/src/kernel/fx-live.test.ts +8 -8
- package/src/kernel/fx.test.ts +17 -11
- package/src/kernel/fx.ts +180 -57
- package/src/kernel/horizontal-child.ts +1 -1
- package/src/kernel/http-query.test.ts +3 -6
- package/src/kernel/index.ts +13 -3
- package/src/kernel/instance-id.ts +1 -1
- package/src/kernel/instances.test.ts +40 -0
- package/src/kernel/instances.ts +47 -9
- package/src/kernel/live-http.test.ts +6 -7
- package/src/kernel/live-http.ts +6 -2
- package/src/kernel/live-resume.test.ts +1 -1
- package/src/kernel/mcp-tool.test.ts +3 -3
- package/src/kernel/on.ts +98 -4
- package/src/kernel/pipeline.test.ts +12 -15
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/stamp-http.test.ts +3 -3
- package/src/kernel/triggers.ts +210 -95
- package/src/kernel-entry.ts +0 -1
- package/src/manifest/diff.ts +11 -1
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/types.ts +46 -2
- package/src/mcp/docs-index.ts +3 -3
- package/src/mcp/docs-mcp.test.ts +2 -2
- package/src/mcp/docs-tools.ts +1 -1
- package/src/okid.test.ts +56 -0
- package/src/okid.ts +55 -11
- package/src/plugins/anonymous.ts +8 -4
- package/src/plugins/auth/shared.ts +27 -3
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/auth-methods.security.test.ts +561 -23
- package/src/plugins/config-source.ts +1 -1
- package/src/plugins/csrf.test.ts +65 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/magic-link.ts +36 -16
- package/src/plugins/oauth/shared.ts +3 -1
- package/src/plugins/oauth.ts +22 -14
- package/src/plugins/otp.ts +37 -21
- package/src/plugins/passkey-webauthn.ts +9 -6
- package/src/plugins/passkey.ts +98 -26
- package/src/plugins/pre-account-hijack.test.ts +223 -0
- package/src/plugins/two-factor.ts +466 -37
- package/src/plugins/username.ts +19 -7
- package/src/release/build-lib.ts +1 -0
- package/src/release/limits.ts +2 -2
- package/src/release/measure.ts +1 -1
- package/src/runs/collect.test.ts +0 -2
- package/src/runtime/json-code-block.test.ts +250 -20
- package/src/runtime/json-code-block.ts +1261 -41
- package/src/term.test.ts +1 -1
- package/src/test/create-test-app.test.ts +12 -12
- package/src/test/live-signals.test.ts +6 -6
- package/src/test/provisions.integration.test.ts +10 -14
- package/src/test/reset-element-registries.ts +1 -1
- package/src/test/tenant-isolation.test.ts +12 -6
- package/site/content/docs/deployment/docker-swarm.mdx +0 -164
- package/site/content/docs/deployment/docker.mdx +0 -227
- package/site/content/docs/deployment/index.mdx +0 -83
- package/site/content/docs/deployment/kubernetes.mdx +0 -176
- package/site/content/docs/deployment/meta.json +0 -5
- package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
- package/site/content/docs/elements/ai.mdx +0 -385
- package/site/content/docs/elements/channel.mdx +0 -346
- package/site/content/docs/elements/clock.mdx +0 -244
- package/site/content/docs/elements/flow.mdx +0 -420
- package/site/content/docs/elements/gate.mdx +0 -436
- package/site/content/docs/elements/signal.mdx +0 -380
- package/site/content/docs/elements/store.mdx +0 -1099
- package/site/content/docs/elements/vault.mdx +0 -405
- package/site/content/docs/get-started/basic-usage.mdx +0 -173
- package/site/content/docs/get-started/index.mdx +0 -43
- package/site/content/docs/get-started/installation.mdx +0 -220
- package/site/content/docs/get-started/introduction.mdx +0 -144
- package/site/content/docs/get-started/meta.json +0 -13
- package/site/content/docs/get-started/project-structure.mdx +0 -925
- package/site/content/docs/get-started/testing.mdx +0 -328
- package/site/content/docs/get-started/why.mdx +0 -114
- package/site/content/docs/recipes/llama-cpp.mdx +0 -151
- package/site/content/docs/recipes/ollama.mdx +0 -142
- package/site/content/docs/recipes/sglang.mdx +0 -105
- package/site/content/docs/recipes/vllm.mdx +0 -106
- package/site/content/docs/reference/cli.md +0 -232
- package/site/content/docs/reference/client.mdx +0 -469
- package/site/content/docs/reference/security.md +0 -74
- package/src/cli/ai-setup/detect-ollama.ts +0 -213
- package/src/cli/ai-setup/recommend.test.ts +0 -225
- package/src/cli/ai-setup/recommend.ts +0 -225
- package/src/cli/dev-controls.test.ts +0 -122
- package/src/cli/dev-controls.ts +0 -164
- package/src/cli/tui/DevLive.tsx +0 -126
- package/src/console/ui-next/dist/assets/access-page-3-EFj-2G.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
- package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-cVFnA4HH.js +0 -1
- package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
- package/src/console/ui-next/dist/assets/index-CMIgUbD0.js +0 -66
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.js +0 -4
- package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
- package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-l8FeKfnP.js +0 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-CL-d_mLE.js +0 -2
- package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
- package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
- package/src/docker/ollama-pull.ts +0 -232
- package/src/docker/recipes/llama-cpp.ts +0 -298
- package/src/docker/recipes/ollama.ts +0 -44
- package/src/docker/recipes/sglang.ts +0 -55
- package/src/docker/recipes/vllm.ts +0 -44
- package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
- package/src/drivers/ai-ollama.integration.test.ts +0 -184
- package/src/drivers/ai-ollama.ts +0 -389
- 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
|
-
});
|
package/src/drivers/ai-ollama.ts
DELETED
|
@@ -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
|
-
}
|
package/src/drivers/ollama.ts
DELETED
|
@@ -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";
|