okengine 0.18.4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +18 -10
- package/README.md +2 -2
- package/package.json +51 -50
- package/site/content/docs/ai/index.mdx +25 -6
- 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 +52 -11
- 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 +78 -30
- package/site/content/docs/meta.json +11 -8
- 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/competitor-mention-removal.test.ts +1 -1
- 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 +258 -44
- 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 -132
- 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 -162
- 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-De7Lc2JC.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-RGy7VEA_.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-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.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-4rHOePuE.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-DISPgxLM.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,213 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Detect local Ollama — CLI (`ollama list` / `ps`) and HTTP `/api/tags`.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { OLLAMA_DEFAULT_BASE_URL, normalizeOllamaBaseUrl } from "../../drivers/ai-ollama.ts";
|
|
6
|
-
import { ALL_CURATED, type CatalogModel } from "./catalog.ts";
|
|
7
|
-
|
|
8
|
-
/** Result of probing the local Ollama install. */
|
|
9
|
-
export type OllamaDetectResult = {
|
|
10
|
-
readonly available: boolean;
|
|
11
|
-
readonly baseUrl: string;
|
|
12
|
-
readonly installed: readonly string[];
|
|
13
|
-
readonly running: readonly string[];
|
|
14
|
-
readonly curatedInstalled: readonly CatalogModel[];
|
|
15
|
-
readonly curatedMissing: readonly CatalogModel[];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Parse `ollama list` tabular output into model names.
|
|
20
|
-
*
|
|
21
|
-
* @param stdout - CLI stdout
|
|
22
|
-
*/
|
|
23
|
-
export function parseOllamaList(stdout: string): string[] {
|
|
24
|
-
const lines = stdout
|
|
25
|
-
.split("\n")
|
|
26
|
-
.map((l) => l.trim())
|
|
27
|
-
.filter(Boolean);
|
|
28
|
-
const out: string[] = [];
|
|
29
|
-
for (const line of lines) {
|
|
30
|
-
if (/^NAME\b/i.test(line)) continue;
|
|
31
|
-
const name = line.split(/\s+/)[0];
|
|
32
|
-
if (name) out.push(name);
|
|
33
|
-
}
|
|
34
|
-
return out;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Parse `ollama ps` tabular output into running model names.
|
|
39
|
-
*
|
|
40
|
-
* @param stdout - CLI stdout
|
|
41
|
-
*/
|
|
42
|
-
export function parseOllamaPs(stdout: string): string[] {
|
|
43
|
-
return parseOllamaList(stdout);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Normalize installed tag for comparison (`qwen3.5:9b` vs `qwen3.5:9b-mlx`).
|
|
48
|
-
*
|
|
49
|
-
* @param id - Catalog or installed id
|
|
50
|
-
* @param installed - Installed names
|
|
51
|
-
*/
|
|
52
|
-
export function isInstalled(id: string, installed: readonly string[]): boolean {
|
|
53
|
-
const base = id.split(":")[0] ?? id;
|
|
54
|
-
return installed.some((name) => {
|
|
55
|
-
if (name === id) return true;
|
|
56
|
-
if (name.startsWith(`${id}-`)) return true;
|
|
57
|
-
if (name.startsWith(`${base}:`) && id.startsWith(`${base}:`)) {
|
|
58
|
-
// treat mlx / quant suffixes as matching the same family:tag when prefix matches
|
|
59
|
-
const instTag = name.slice(base.length + 1);
|
|
60
|
-
const wantTag = id.slice(base.length + 1);
|
|
61
|
-
return (
|
|
62
|
-
instTag === wantTag || instTag.startsWith(`${wantTag}-`) || wantTag.startsWith(instTag)
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
return false;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Detect Ollama via CLI then HTTP fallback.
|
|
71
|
-
*
|
|
72
|
-
* @param options - Base URL / spawn / fetch seams
|
|
73
|
-
*/
|
|
74
|
-
export async function detectOllama(
|
|
75
|
-
options: {
|
|
76
|
-
readonly baseUrl?: string;
|
|
77
|
-
readonly fetch?: typeof globalThis.fetch;
|
|
78
|
-
readonly run?: (cmd: readonly string[]) => Promise<{ code: number; stdout: string }>;
|
|
79
|
-
} = {},
|
|
80
|
-
): Promise<OllamaDetectResult> {
|
|
81
|
-
const baseUrl = normalizeOllamaBaseUrl(options.baseUrl ?? OLLAMA_DEFAULT_BASE_URL);
|
|
82
|
-
const run =
|
|
83
|
-
options.run ??
|
|
84
|
-
(async (cmd) => {
|
|
85
|
-
const proc = Bun.spawn([...cmd], { stdout: "pipe", stderr: "pipe" });
|
|
86
|
-
const stdout = await new Response(proc.stdout).text();
|
|
87
|
-
const code = await proc.exited;
|
|
88
|
-
return { code, stdout };
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
let installed: string[] = [];
|
|
92
|
-
let running: string[] = [];
|
|
93
|
-
let available = false;
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const list = await run(["ollama", "list"]);
|
|
97
|
-
if (list.code === 0) {
|
|
98
|
-
available = true;
|
|
99
|
-
installed = parseOllamaList(list.stdout);
|
|
100
|
-
}
|
|
101
|
-
const ps = await run(["ollama", "ps"]);
|
|
102
|
-
if (ps.code === 0) {
|
|
103
|
-
running = parseOllamaPs(ps.stdout);
|
|
104
|
-
}
|
|
105
|
-
} catch {
|
|
106
|
-
// CLI missing — try HTTP
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (!available) {
|
|
110
|
-
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
111
|
-
try {
|
|
112
|
-
const res = await fetchFn(`${baseUrl}/api/tags`, {
|
|
113
|
-
method: "GET",
|
|
114
|
-
signal: AbortSignal.timeout(2_000),
|
|
115
|
-
});
|
|
116
|
-
if (res.ok) {
|
|
117
|
-
available = true;
|
|
118
|
-
const json = (await res.json()) as { models?: readonly { name?: string }[] };
|
|
119
|
-
installed = (json.models ?? [])
|
|
120
|
-
.map((m) => m.name)
|
|
121
|
-
.filter((n): n is string => typeof n === "string");
|
|
122
|
-
}
|
|
123
|
-
} catch {
|
|
124
|
-
// unavailable
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const curatedInstalled = ALL_CURATED.filter((m) => isInstalled(m.id, installed));
|
|
129
|
-
const curatedMissing = ALL_CURATED.filter((m) => !isInstalled(m.id, installed));
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
available,
|
|
133
|
-
baseUrl,
|
|
134
|
-
installed,
|
|
135
|
-
running,
|
|
136
|
-
curatedInstalled,
|
|
137
|
-
curatedMissing,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Detected host hardware for the Ollama banner. */
|
|
142
|
-
export type MachineInfo = {
|
|
143
|
-
readonly osName: string;
|
|
144
|
-
readonly cpuCount: number | null;
|
|
145
|
-
readonly ramGb: number | null;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Best-effort total system RAM in GB.
|
|
150
|
-
*/
|
|
151
|
-
export function detectTotalRamGb(): number | null {
|
|
152
|
-
try {
|
|
153
|
-
if (process.platform === "darwin") {
|
|
154
|
-
const proc = Bun.spawnSync(["sysctl", "-n", "hw.memsize"], { stdout: "pipe" });
|
|
155
|
-
if (proc.exitCode === 0) {
|
|
156
|
-
const bytes = Number(proc.stdout.toString().trim());
|
|
157
|
-
if (Number.isFinite(bytes) && bytes > 0) return Math.round(bytes / 1024 ** 3);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (process.platform === "linux") {
|
|
161
|
-
const proc = Bun.spawnSync(["awk", "/MemTotal/ {print $2}", "/proc/meminfo"], {
|
|
162
|
-
stdout: "pipe",
|
|
163
|
-
});
|
|
164
|
-
if (proc.exitCode === 0) {
|
|
165
|
-
const kb = Number(proc.stdout.toString().trim());
|
|
166
|
-
if (Number.isFinite(kb) && kb > 0) return Math.round(kb / 1024 ** 2);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
} catch {
|
|
170
|
-
// ignore
|
|
171
|
-
}
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Best-effort OS name · CPU count · RAM for the Ollama banner.
|
|
177
|
-
*/
|
|
178
|
-
export function detectMachineInfo(): MachineInfo {
|
|
179
|
-
const osName =
|
|
180
|
-
process.platform === "darwin"
|
|
181
|
-
? "macOS"
|
|
182
|
-
: process.platform === "linux"
|
|
183
|
-
? "Linux"
|
|
184
|
-
: process.platform === "win32"
|
|
185
|
-
? "Windows"
|
|
186
|
-
: process.platform;
|
|
187
|
-
|
|
188
|
-
let cpuCount: number | null = null;
|
|
189
|
-
try {
|
|
190
|
-
if (process.platform === "darwin") {
|
|
191
|
-
const proc = Bun.spawnSync(["sysctl", "-n", "hw.ncpu"], { stdout: "pipe" });
|
|
192
|
-
if (proc.exitCode === 0) {
|
|
193
|
-
const n = Number(proc.stdout.toString().trim());
|
|
194
|
-
if (Number.isFinite(n) && n > 0) cpuCount = n;
|
|
195
|
-
}
|
|
196
|
-
} else if (typeof navigator !== "undefined" && "hardwareConcurrency" in navigator) {
|
|
197
|
-
const n = Number(navigator.hardwareConcurrency);
|
|
198
|
-
if (Number.isFinite(n) && n > 0) cpuCount = n;
|
|
199
|
-
}
|
|
200
|
-
} catch {
|
|
201
|
-
// ignore
|
|
202
|
-
}
|
|
203
|
-
if (cpuCount === null) {
|
|
204
|
-
try {
|
|
205
|
-
const n = (process as { availableParallelism?: () => number }).availableParallelism?.();
|
|
206
|
-
if (typeof n === "number" && Number.isFinite(n) && n > 0) cpuCount = n;
|
|
207
|
-
} catch {
|
|
208
|
-
// ignore
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return { osName, cpuCount, ramGb: detectTotalRamGb() };
|
|
213
|
-
}
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smart Ollama recommendation — RAM tiers (no needs quiz).
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, expect, test } from "bun:test";
|
|
6
|
-
import {
|
|
7
|
-
LLAMA_CPP_CHAT_MODELS,
|
|
8
|
-
llamaCppModelsForTier,
|
|
9
|
-
modelsForTier,
|
|
10
|
-
recommendChatModel,
|
|
11
|
-
recommendForTier,
|
|
12
|
-
recommendLlamaCppForTier,
|
|
13
|
-
} from "./catalog.ts";
|
|
14
|
-
import {
|
|
15
|
-
fittingChatModels,
|
|
16
|
-
formatLlamaCppBanner,
|
|
17
|
-
formatModelRow,
|
|
18
|
-
formatModelTableHeader,
|
|
19
|
-
formatOllamaBanner,
|
|
20
|
-
isTightFit,
|
|
21
|
-
modelFitsComfortably,
|
|
22
|
-
modelFitsOnMachine,
|
|
23
|
-
recommendChatForNeeds,
|
|
24
|
-
suggestTierForRam,
|
|
25
|
-
usableRamGb,
|
|
26
|
-
} from "./recommend.ts";
|
|
27
|
-
|
|
28
|
-
describe("modelFitsOnMachine", () => {
|
|
29
|
-
test("catalog ramGb is machine tier, not download size", () => {
|
|
30
|
-
expect(
|
|
31
|
-
modelFitsOnMachine(
|
|
32
|
-
{
|
|
33
|
-
id: "x",
|
|
34
|
-
label: "x",
|
|
35
|
-
hint: "",
|
|
36
|
-
role: "chat",
|
|
37
|
-
ramGb: 16,
|
|
38
|
-
tier: "balanced",
|
|
39
|
-
modalities: ["text"],
|
|
40
|
-
},
|
|
41
|
-
24,
|
|
42
|
-
),
|
|
43
|
-
).toBe(true);
|
|
44
|
-
expect(
|
|
45
|
-
modelFitsOnMachine(
|
|
46
|
-
{
|
|
47
|
-
id: "x",
|
|
48
|
-
label: "x",
|
|
49
|
-
hint: "",
|
|
50
|
-
role: "chat",
|
|
51
|
-
ramGb: 32,
|
|
52
|
-
tier: "smart",
|
|
53
|
-
modalities: ["text"],
|
|
54
|
-
},
|
|
55
|
-
24,
|
|
56
|
-
),
|
|
57
|
-
).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe("tiers", () => {
|
|
62
|
-
test("each tier exposes up to 10 models", () => {
|
|
63
|
-
for (const tier of ["ultra-fast", "fast", "balanced", "smart"] as const) {
|
|
64
|
-
expect(modelsForTier(tier).length).toBeGreaterThan(0);
|
|
65
|
-
expect(modelsForTier(tier).length).toBeLessThanOrEqual(10);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("suggestTierForRam maps fit budget", () => {
|
|
70
|
-
expect(suggestTierForRam(4)).toBe("ultra-fast");
|
|
71
|
-
expect(suggestTierForRam(8)).toBe("fast");
|
|
72
|
-
expect(suggestTierForRam(16)).toBe("balanced");
|
|
73
|
-
expect(suggestTierForRam(32)).toBe("smart");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("recommendForTier returns a model in that tier", () => {
|
|
77
|
-
const pick = recommendForTier("fast", 16);
|
|
78
|
-
expect(pick.tier).toBe("fast");
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe("recommendChatForNeeds", () => {
|
|
83
|
-
test("8GB → ultra-fast or fast entry", () => {
|
|
84
|
-
const pick = recommendChatForNeeds(8);
|
|
85
|
-
expect(pick.ramGb).toBeLessThanOrEqual(8);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("16GB → never recommends 32GB-class", () => {
|
|
89
|
-
const pick = recommendChatForNeeds(16);
|
|
90
|
-
expect(pick.ramGb).toBeLessThanOrEqual(16);
|
|
91
|
-
expect(pick.id).not.toBe("qwen3.5:27b");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("24GB leaves headroom", () => {
|
|
95
|
-
const pick = recommendChatForNeeds(24);
|
|
96
|
-
expect(pick.ramGb).toBeLessThanOrEqual(usableRamGb(24));
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe("comfortable vs tight", () => {
|
|
101
|
-
test("16GB-class on 16GB machine is tight", () => {
|
|
102
|
-
const qwen = fittingChatModels(16).find((m) => m.id === "qwen3.5:9b");
|
|
103
|
-
expect(qwen).toBeDefined();
|
|
104
|
-
expect(modelFitsOnMachine(qwen!, 16)).toBe(true);
|
|
105
|
-
expect(modelFitsComfortably(qwen!, 16)).toBe(false);
|
|
106
|
-
expect(isTightFit(qwen!, 16)).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("16GB-class on 24GB machine is comfortable", () => {
|
|
110
|
-
const qwen = fittingChatModels(24).find((m) => m.id === "qwen3.5:9b")!;
|
|
111
|
-
expect(modelFitsComfortably(qwen, 24)).toBe(true);
|
|
112
|
-
expect(isTightFit(qwen, 24)).toBe(false);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe("recommendChatModel", () => {
|
|
117
|
-
test("respects tiers with headroom preference", () => {
|
|
118
|
-
expect(recommendChatModel(8).ramGb).toBeLessThanOrEqual(8);
|
|
119
|
-
expect(recommendChatModel(16).ramGb).toBeLessThanOrEqual(16);
|
|
120
|
-
expect(recommendChatModel(24).ramGb).toBeLessThanOrEqual(16);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe("formatOllamaBanner", () => {
|
|
125
|
-
test("shows OS · CPU · RAM · Fit RAM · detected — no comfortable list", () => {
|
|
126
|
-
const text = formatOllamaBanner({ osName: "macOS", cpuCount: 10, ramGb: 24 }, ["gemma4:e4b"]);
|
|
127
|
-
expect(text).toContain("OS macOS · CPU 10 · RAM ~24GB RAM");
|
|
128
|
-
expect(text).toContain("Fit RAM ~20GB RAM");
|
|
129
|
-
expect(text).toContain("Detected local models");
|
|
130
|
-
expect(text).toContain("gemma4:e4b");
|
|
131
|
-
expect(text).not.toContain("Fits comfortably");
|
|
132
|
-
expect(text).toContain("Tiers · Ultra Fast");
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe("llama.cpp catalog", () => {
|
|
137
|
-
test("each tier has ≥10 curated Docker Hub ai/ models (UI shows up to 20)", () => {
|
|
138
|
-
for (const tier of ["ultra-fast", "fast", "balanced", "smart"] as const) {
|
|
139
|
-
const all = LLAMA_CPP_CHAT_MODELS.filter((m) => m.tier === tier);
|
|
140
|
-
expect(all.length).toBeGreaterThanOrEqual(10);
|
|
141
|
-
const list = llamaCppModelsForTier(tier);
|
|
142
|
-
expect(list.length).toBeGreaterThanOrEqual(10);
|
|
143
|
-
expect(list.length).toBeLessThanOrEqual(20);
|
|
144
|
-
expect(list.length).toBe(Math.min(all.length, 20));
|
|
145
|
-
expect(list.every((m) => !m.id.startsWith("ai/"))).toBe(true);
|
|
146
|
-
for (let i = 1; i < list.length; i++) {
|
|
147
|
-
expect(list[i]!.ramGb).toBeGreaterThanOrEqual(list[i - 1]!.ramGb);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("recommendLlamaCppForTier returns a model in that tier", () => {
|
|
153
|
-
expect(recommendLlamaCppForTier("ultra-fast", 8).id).toBe("granite3.3:2b");
|
|
154
|
-
expect(recommendLlamaCppForTier("fast", 16).tier).toBe("fast");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("visible list surfaces Gemma 4 and Qwen", () => {
|
|
158
|
-
const ids = (tier: "fast" | "balanced" | "smart") =>
|
|
159
|
-
llamaCppModelsForTier(tier).map((m) => m.id);
|
|
160
|
-
expect(ids("fast").some((id) => id.startsWith("gemma4:"))).toBe(true);
|
|
161
|
-
expect(ids("fast").some((id) => id.startsWith("qwen"))).toBe(true);
|
|
162
|
-
expect(ids("balanced")).toContain("gemma4:e4b");
|
|
163
|
-
expect(ids("balanced").some((id) => id.startsWith("qwen3"))).toBe(true);
|
|
164
|
-
expect(ids("smart")).toContain("gemma4:31b");
|
|
165
|
-
expect(ids("smart").some((id) => id.startsWith("qwen"))).toBe(true);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("formatLlamaCppBanner shows Docker Hub ai/ source — not Ollama detect", () => {
|
|
169
|
-
const text = formatLlamaCppBanner({ osName: "macOS", cpuCount: 10, ramGb: 24 });
|
|
170
|
-
expect(text).toContain("OS macOS · CPU 10 · RAM ~24GB RAM");
|
|
171
|
-
expect(text).toContain("Fit RAM ~20GB RAM");
|
|
172
|
-
expect(text).toContain("Docker Hub ai/");
|
|
173
|
-
expect(text).not.toContain("Detected local models");
|
|
174
|
-
expect(text).toContain("Tiers · Ultra Fast");
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe("formatModelRow", () => {
|
|
179
|
-
test("aligned Model | RAM | Caps columns", () => {
|
|
180
|
-
const row = formatModelRow({
|
|
181
|
-
id: "x",
|
|
182
|
-
label: "Gemma 4 4B",
|
|
183
|
-
hint: "",
|
|
184
|
-
role: "chat",
|
|
185
|
-
ramGb: 8,
|
|
186
|
-
tier: "fast",
|
|
187
|
-
modalities: ["text", "code"],
|
|
188
|
-
});
|
|
189
|
-
const header = formatModelTableHeader();
|
|
190
|
-
expect(header.startsWith("Model")).toBe(true);
|
|
191
|
-
expect(header).toContain("RAM");
|
|
192
|
-
expect(header).toContain("Caps");
|
|
193
|
-
// Caps column starts at the same offset as the header.
|
|
194
|
-
expect(row.indexOf("text")).toBe(header.indexOf("Caps"));
|
|
195
|
-
expect(row).toContain("Gemma 4 4B");
|
|
196
|
-
expect(row).toContain("≈8GB");
|
|
197
|
-
expect(row).toContain("text · code");
|
|
198
|
-
expect(row).not.toContain(" · ≈");
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("Arabic and CJK labels keep Caps aligned", () => {
|
|
202
|
-
const header = formatModelTableHeader();
|
|
203
|
-
const capsWidth = Bun.stringWidth(header.slice(0, header.indexOf("Caps")));
|
|
204
|
-
const ar = formatModelRow({
|
|
205
|
-
id: "ar",
|
|
206
|
-
label: "مرحبا بالعالم",
|
|
207
|
-
hint: "",
|
|
208
|
-
role: "chat",
|
|
209
|
-
ramGb: 8,
|
|
210
|
-
tier: "fast",
|
|
211
|
-
modalities: ["text"],
|
|
212
|
-
});
|
|
213
|
-
const cjk = formatModelRow({
|
|
214
|
-
id: "cjk",
|
|
215
|
-
label: "你好世界模型",
|
|
216
|
-
hint: "",
|
|
217
|
-
role: "chat",
|
|
218
|
-
ramGb: 8,
|
|
219
|
-
tier: "fast",
|
|
220
|
-
modalities: ["text"],
|
|
221
|
-
});
|
|
222
|
-
expect(Bun.stringWidth(ar.slice(0, ar.indexOf("text")))).toBe(capsWidth);
|
|
223
|
-
expect(Bun.stringWidth(cjk.slice(0, cjk.indexOf("text")))).toBe(capsWidth);
|
|
224
|
-
});
|
|
225
|
-
});
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Machine fit helpers for local Ollama model picks.
|
|
3
|
-
*
|
|
4
|
-
* Catalog `ramGb` is the **recommended machine tier** for that model (not the
|
|
5
|
-
* download size). Prefer picks that leave ~4GB for OS / IDE / browser.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
CHAT_MODELS,
|
|
10
|
-
MODEL_TIERS,
|
|
11
|
-
type CatalogModel,
|
|
12
|
-
type ModelTier,
|
|
13
|
-
recommendForTier,
|
|
14
|
-
} from "./catalog.ts";
|
|
15
|
-
|
|
16
|
-
/** Reserved for OS + IDE + browser while the model runs. */
|
|
17
|
-
export const OS_HEADROOM_GB = 4;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* RAM left after reserving OS/IDE headroom.
|
|
21
|
-
*
|
|
22
|
-
* @param totalRamGb - Detected machine RAM
|
|
23
|
-
*/
|
|
24
|
-
export function usableRamGb(totalRamGb: number): number {
|
|
25
|
-
return Math.max(0, totalRamGb - OS_HEADROOM_GB);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Whether the machine meets the model's recommended tier (`model.ramGb`).
|
|
30
|
-
*
|
|
31
|
-
* @param model - Catalog entry
|
|
32
|
-
* @param totalRamGb - Machine RAM (null → only the catalog default recommended)
|
|
33
|
-
*/
|
|
34
|
-
export function modelFitsOnMachine(model: CatalogModel, totalRamGb: number | null): boolean {
|
|
35
|
-
if (totalRamGb === null || !Number.isFinite(totalRamGb)) {
|
|
36
|
-
return Boolean(model.recommended);
|
|
37
|
-
}
|
|
38
|
-
return model.ramGb <= totalRamGb;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Whether the model fits with comfortable headroom for OS/IDE.
|
|
43
|
-
*
|
|
44
|
-
* @param model - Catalog entry
|
|
45
|
-
* @param totalRamGb - Machine RAM
|
|
46
|
-
*/
|
|
47
|
-
export function modelFitsComfortably(model: CatalogModel, totalRamGb: number | null): boolean {
|
|
48
|
-
if (totalRamGb === null || !Number.isFinite(totalRamGb)) {
|
|
49
|
-
return Boolean(model.recommended);
|
|
50
|
-
}
|
|
51
|
-
const smallest = [...CHAT_MODELS].sort((a, b) => a.ramGb - b.ramGb)[0]!;
|
|
52
|
-
if (model.id === smallest.id) return model.ramGb <= totalRamGb;
|
|
53
|
-
return model.ramGb + OS_HEADROOM_GB <= totalRamGb;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* True when the model meets the tier but leaves little room for OS/IDE.
|
|
58
|
-
*
|
|
59
|
-
* @param model - Catalog entry
|
|
60
|
-
* @param totalRamGb - Machine RAM
|
|
61
|
-
*/
|
|
62
|
-
export function isTightFit(model: CatalogModel, totalRamGb: number | null): boolean {
|
|
63
|
-
if (totalRamGb === null || !Number.isFinite(totalRamGb)) return false;
|
|
64
|
-
return modelFitsOnMachine(model, totalRamGb) && !modelFitsComfortably(model, totalRamGb);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Chat models that meet the machine tier, smallest → largest.
|
|
69
|
-
*
|
|
70
|
-
* @param totalRamGb - Machine RAM
|
|
71
|
-
*/
|
|
72
|
-
export function fittingChatModels(totalRamGb: number | null): readonly CatalogModel[] {
|
|
73
|
-
const fits = CHAT_MODELS.filter((m) => modelFitsOnMachine(m, totalRamGb));
|
|
74
|
-
if (fits.length > 0) return [...fits].sort((a, b) => a.ramGb - b.ramGb);
|
|
75
|
-
return [[...CHAT_MODELS].sort((a, b) => a.ramGb - b.ramGb)[0]!];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Prefer comfortable fits; fall back to any tier-fit if needed.
|
|
80
|
-
*
|
|
81
|
-
* @param totalRamGb - Machine RAM
|
|
82
|
-
*/
|
|
83
|
-
export function comfortableChatModels(totalRamGb: number | null): readonly CatalogModel[] {
|
|
84
|
-
const comfortable = CHAT_MODELS.filter((m) => modelFitsComfortably(m, totalRamGb));
|
|
85
|
-
if (comfortable.length > 0) {
|
|
86
|
-
return [...comfortable].sort((a, b) => a.ramGb - b.ramGb);
|
|
87
|
-
}
|
|
88
|
-
return fittingChatModels(totalRamGb);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Suggest a tier for the machine's fit RAM budget.
|
|
93
|
-
*
|
|
94
|
-
* @param totalRamGb - Machine RAM
|
|
95
|
-
*/
|
|
96
|
-
export function suggestTierForRam(totalRamGb: number | null): ModelTier {
|
|
97
|
-
if (totalRamGb === null || !Number.isFinite(totalRamGb)) return "fast";
|
|
98
|
-
const budget = usableRamGb(totalRamGb);
|
|
99
|
-
if (budget >= 24) return "smart";
|
|
100
|
-
if (budget >= 8) return "balanced";
|
|
101
|
-
if (budget >= 4) return "fast";
|
|
102
|
-
return "ultra-fast";
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Recommend a chat model for host RAM (balanced default — no quiz).
|
|
107
|
-
*
|
|
108
|
-
* @param totalRamGb - Machine RAM
|
|
109
|
-
*/
|
|
110
|
-
export function recommendChatForNeeds(totalRamGb: number | null): CatalogModel {
|
|
111
|
-
return recommendForTier(suggestTierForRam(totalRamGb), totalRamGb);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Shared hardware lines for local AI banners.
|
|
116
|
-
*
|
|
117
|
-
* @param machine - Detected hardware
|
|
118
|
-
*/
|
|
119
|
-
function formatMachineBannerLines(machine: {
|
|
120
|
-
readonly osName: string;
|
|
121
|
-
readonly cpuCount: number | null;
|
|
122
|
-
readonly ramGb: number | null;
|
|
123
|
-
}): readonly string[] {
|
|
124
|
-
const ram =
|
|
125
|
-
machine.ramGb !== null && Number.isFinite(machine.ramGb)
|
|
126
|
-
? `~${machine.ramGb}GB RAM`
|
|
127
|
-
: "RAM unknown";
|
|
128
|
-
const cpu =
|
|
129
|
-
machine.cpuCount !== null && Number.isFinite(machine.cpuCount) ? String(machine.cpuCount) : "?";
|
|
130
|
-
const fit =
|
|
131
|
-
machine.ramGb !== null && Number.isFinite(machine.ramGb)
|
|
132
|
-
? `~${usableRamGb(machine.ramGb)}GB RAM`
|
|
133
|
-
: "unknown";
|
|
134
|
-
const sep = " · ";
|
|
135
|
-
return [[`OS ${machine.osName}`, `CPU ${cpu}`, `RAM ${ram}`].join(sep), `Fit RAM ${fit}`];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Ollama banner lines — OS · CPU · RAM · fit · detected · tiers.
|
|
140
|
-
*
|
|
141
|
-
* @param machine - Detected hardware
|
|
142
|
-
* @param detectedIds - Installed model ids
|
|
143
|
-
*/
|
|
144
|
-
export function formatOllamaBanner(
|
|
145
|
-
machine: {
|
|
146
|
-
readonly osName: string;
|
|
147
|
-
readonly cpuCount: number | null;
|
|
148
|
-
readonly ramGb: number | null;
|
|
149
|
-
},
|
|
150
|
-
detectedIds: readonly string[],
|
|
151
|
-
): string {
|
|
152
|
-
const sep = " · ";
|
|
153
|
-
const lines = [
|
|
154
|
-
...formatMachineBannerLines(machine),
|
|
155
|
-
"",
|
|
156
|
-
"Detected local models",
|
|
157
|
-
...(detectedIds.length > 0 ? detectedIds.map((id) => id) : ["(none detected)"]),
|
|
158
|
-
"",
|
|
159
|
-
`Tiers${sep}${MODEL_TIERS.map((t) => t.label).join(sep)}`,
|
|
160
|
-
];
|
|
161
|
-
return lines.join("\n");
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* llama.cpp banner — OS · CPU · RAM · fit · Docker Hub `ai/` source · tiers.
|
|
166
|
-
*
|
|
167
|
-
* @param machine - Detected hardware
|
|
168
|
-
*/
|
|
169
|
-
export function formatLlamaCppBanner(machine: {
|
|
170
|
-
readonly osName: string;
|
|
171
|
-
readonly cpuCount: number | null;
|
|
172
|
-
readonly ramGb: number | null;
|
|
173
|
-
}): string {
|
|
174
|
-
const sep = " · ";
|
|
175
|
-
const lines = [
|
|
176
|
-
...formatMachineBannerLines(machine),
|
|
177
|
-
"",
|
|
178
|
-
"Model source",
|
|
179
|
-
"Docker Hub ai/ (curated) — pulled on first container start",
|
|
180
|
-
"",
|
|
181
|
-
`Tiers${sep}${MODEL_TIERS.map((t) => t.label).join(sep)}`,
|
|
182
|
-
];
|
|
183
|
-
return lines.join("\n");
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** Model name column width (monospace CLI table). */
|
|
187
|
-
const MODEL_COL_NAME = 26;
|
|
188
|
-
/** RAM column width, right-aligned (`≈32GB`). */
|
|
189
|
-
const MODEL_COL_RAM = 7;
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Clip or pad a cell for aligned CLI columns.
|
|
193
|
-
*
|
|
194
|
-
* @param value - Cell text
|
|
195
|
-
* @param width - Fixed width
|
|
196
|
-
*/
|
|
197
|
-
function clipPad(value: string, width: number): string {
|
|
198
|
-
const bunAnsi = Bun as typeof Bun & {
|
|
199
|
-
stringWidth(text: string): number;
|
|
200
|
-
sliceAnsi(text: string, start: number, end: number, omission?: string): string;
|
|
201
|
-
};
|
|
202
|
-
const display = bunAnsi.stringWidth(value);
|
|
203
|
-
if (display === width) return value;
|
|
204
|
-
if (display < width) return value + " ".repeat(width - display);
|
|
205
|
-
return bunAnsi.sliceAnsi(value, 0, width, "…");
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Column header for the model pick list (aligns with {@link formatModelRow}).
|
|
210
|
-
*/
|
|
211
|
-
export function formatModelTableHeader(): string {
|
|
212
|
-
return `${clipPad("Model", MODEL_COL_NAME)} ${"RAM".padStart(MODEL_COL_RAM)} Caps`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* One model row as aligned columns: Model | RAM | Caps.
|
|
217
|
-
*
|
|
218
|
-
* @param model - Catalog entry
|
|
219
|
-
*/
|
|
220
|
-
export function formatModelRow(model: CatalogModel): string {
|
|
221
|
-
const name = clipPad(model.label, MODEL_COL_NAME);
|
|
222
|
-
const ram = `≈${model.ramGb}GB`.padStart(MODEL_COL_RAM);
|
|
223
|
-
const caps = model.modalities.join(" · ");
|
|
224
|
-
return `${name} ${ram} ${caps}`;
|
|
225
|
-
}
|