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,232 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pull an Ollama model via the container's HTTP API on its exposed host port.
|
|
3
|
-
*
|
|
4
|
-
* Never shells out to a host `ollama` CLI — that talks to whichever server wins
|
|
5
|
-
* on the default local port (often a separately installed host daemon), so the
|
|
6
|
-
* model can land outside the recipe container.
|
|
7
|
-
*
|
|
8
|
-
* Skips `/api/pull` when the model is already listed on `/api/tags`.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/** Fetch contract (injectable for tests) — call signature only, not Bun's `preconnect`. */
|
|
12
|
-
export type OllamaFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
13
|
-
|
|
14
|
-
/** Options for {@link ensureOllamaModel}. */
|
|
15
|
-
export interface EnsureOllamaModelOptions {
|
|
16
|
-
/** Base URL of the container API, e.g. `http://127.0.0.1:11434`. */
|
|
17
|
-
readonly url: string;
|
|
18
|
-
/** Model tag to pull (e.g. `qwen3.5:9b`). */
|
|
19
|
-
readonly model: string;
|
|
20
|
-
readonly fetch?: OllamaFetch;
|
|
21
|
-
/** How long to wait for `/api/tags` before giving up (ms). Default 90s. */
|
|
22
|
-
readonly readyTimeoutMs?: number;
|
|
23
|
-
/** Overall pull deadline (ms). Default 20 minutes. */
|
|
24
|
-
readonly pullTimeoutMs?: number;
|
|
25
|
-
/** Progress / status lines (e.g. `oke: …`). */
|
|
26
|
-
readonly onStatus?: (line: string) => void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Fail-loud pull / readiness error. */
|
|
30
|
-
export class OllamaPullError extends Error {
|
|
31
|
-
constructor(message: string) {
|
|
32
|
-
super(message);
|
|
33
|
-
this.name = "OllamaPullError";
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Normalize a base URL (trim trailing slash).
|
|
39
|
-
*
|
|
40
|
-
* @param url - Raw base URL
|
|
41
|
-
*/
|
|
42
|
-
export function normalizeOllamaPullUrl(url: string): string {
|
|
43
|
-
return url.replace(/\/+$/, "");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Whether an installed tag covers the requested model id.
|
|
48
|
-
*
|
|
49
|
-
* @param want - Requested id (e.g. `gemma4:e4b`)
|
|
50
|
-
* @param installed - Names from `/api/tags`
|
|
51
|
-
*/
|
|
52
|
-
export function ollamaTagsInclude(want: string, installed: readonly string[]): boolean {
|
|
53
|
-
const id = want.trim();
|
|
54
|
-
if (!id) return false;
|
|
55
|
-
const base = id.split(":")[0] ?? id;
|
|
56
|
-
return installed.some((name) => {
|
|
57
|
-
if (name === id) return true;
|
|
58
|
-
if (name.startsWith(`${id}-`)) return true;
|
|
59
|
-
if (name.startsWith(`${base}:`) && id.startsWith(`${base}:`)) {
|
|
60
|
-
const instTag = name.slice(base.length + 1);
|
|
61
|
-
const wantTag = id.slice(base.length + 1);
|
|
62
|
-
return (
|
|
63
|
-
instTag === wantTag ||
|
|
64
|
-
instTag.startsWith(`${wantTag}-`) ||
|
|
65
|
-
wantTag.startsWith(instTag) ||
|
|
66
|
-
(instTag === "latest" && wantTag === "latest")
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
// bare name matches `name:latest`
|
|
70
|
-
if (name === `${id}:latest` || id === `${name}:latest`) return true;
|
|
71
|
-
return false;
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Parse model names from an `/api/tags` JSON body.
|
|
77
|
-
*
|
|
78
|
-
* @param json - Parsed response
|
|
79
|
-
*/
|
|
80
|
-
export function parseOllamaTagsNames(json: unknown): string[] {
|
|
81
|
-
if (!json || typeof json !== "object") return [];
|
|
82
|
-
const models = (json as { models?: unknown }).models;
|
|
83
|
-
if (!Array.isArray(models)) return [];
|
|
84
|
-
return models
|
|
85
|
-
.map((m) =>
|
|
86
|
-
m && typeof m === "object" && typeof (m as { name?: unknown }).name === "string"
|
|
87
|
-
? (m as { name: string }).name
|
|
88
|
-
: null,
|
|
89
|
-
)
|
|
90
|
-
.filter((n): n is string => typeof n === "string" && n.length > 0);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Wait until `GET /api/tags` succeeds; skip pull when the model is already
|
|
95
|
-
* present; otherwise `POST /api/pull` with streaming progress.
|
|
96
|
-
*
|
|
97
|
-
* @param opts - Target URL + model (+ optional injectable fetch)
|
|
98
|
-
*/
|
|
99
|
-
export async function ensureOllamaModel(opts: EnsureOllamaModelOptions): Promise<void> {
|
|
100
|
-
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
101
|
-
const base = normalizeOllamaPullUrl(opts.url);
|
|
102
|
-
const model = opts.model.trim();
|
|
103
|
-
const status = opts.onStatus ?? (() => {});
|
|
104
|
-
if (!model) throw new OllamaPullError("ollama pull: model id is empty");
|
|
105
|
-
|
|
106
|
-
const readyDeadline = Date.now() + (opts.readyTimeoutMs ?? 90_000);
|
|
107
|
-
let tagsBody: unknown = null;
|
|
108
|
-
while (Date.now() < readyDeadline) {
|
|
109
|
-
try {
|
|
110
|
-
const res = await fetchFn(`${base}/api/tags`, { method: "GET" });
|
|
111
|
-
if (res.ok) {
|
|
112
|
-
tagsBody = await res.json().catch(() => null);
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
} catch {
|
|
116
|
-
// still starting
|
|
117
|
-
}
|
|
118
|
-
await Bun.sleep(500);
|
|
119
|
-
}
|
|
120
|
-
if (tagsBody === null) {
|
|
121
|
-
throw new OllamaPullError(`ollama pull: unreachable at ${base}/api/tags`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const installed = parseOllamaTagsNames(tagsBody);
|
|
125
|
-
if (ollamaTagsInclude(model, installed)) {
|
|
126
|
-
status(`oke: Ollama already has ${model} at ${base} — skip pull`);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
status(`oke: pulling ${model} into Ollama at ${base}…`);
|
|
131
|
-
|
|
132
|
-
const pullDeadline = Date.now() + (opts.pullTimeoutMs ?? 20 * 60_000);
|
|
133
|
-
const controller = new AbortController();
|
|
134
|
-
const timer = setTimeout(() => controller.abort(), Math.max(1, pullDeadline - Date.now()));
|
|
135
|
-
try {
|
|
136
|
-
const res = await fetchFn(`${base}/api/pull`, {
|
|
137
|
-
method: "POST",
|
|
138
|
-
headers: { "content-type": "application/json" },
|
|
139
|
-
body: JSON.stringify({ model, stream: true }),
|
|
140
|
-
signal: controller.signal,
|
|
141
|
-
});
|
|
142
|
-
if (!res.ok) {
|
|
143
|
-
const text = await res.text().catch(() => "");
|
|
144
|
-
throw new OllamaPullError(
|
|
145
|
-
`ollama pull: POST ${base}/api/pull → ${res.status}${text ? ` ${text.slice(0, 200)}` : ""}`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
await consumeOllamaPullStream(res, status);
|
|
149
|
-
} catch (err) {
|
|
150
|
-
if (err instanceof OllamaPullError) throw err;
|
|
151
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
152
|
-
throw new OllamaPullError(`ollama pull: POST ${base}/api/pull failed — ${msg}`);
|
|
153
|
-
} finally {
|
|
154
|
-
clearTimeout(timer);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Read NDJSON pull progress; surface concise status updates.
|
|
160
|
-
*
|
|
161
|
-
* @param res - Streaming pull response
|
|
162
|
-
* @param status - Status writer
|
|
163
|
-
*/
|
|
164
|
-
async function consumeOllamaPullStream(
|
|
165
|
-
res: Response,
|
|
166
|
-
status: (line: string) => void,
|
|
167
|
-
): Promise<void> {
|
|
168
|
-
// Non-body responses (mocked tests) — treat as complete.
|
|
169
|
-
if (!res.body) {
|
|
170
|
-
await res.arrayBuffer().catch(() => undefined);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const reader = res.body.getReader();
|
|
175
|
-
const decoder = new TextDecoder();
|
|
176
|
-
let buf = "";
|
|
177
|
-
const state: PullStreamState = { lastStatus: "", lastPct: -1 };
|
|
178
|
-
|
|
179
|
-
for (;;) {
|
|
180
|
-
const { done, value } = await reader.read();
|
|
181
|
-
if (done) break;
|
|
182
|
-
buf += decoder.decode(value, { stream: true });
|
|
183
|
-
const lines = buf.split("\n");
|
|
184
|
-
buf = lines.pop() ?? "";
|
|
185
|
-
for (const line of lines) {
|
|
186
|
-
handlePullLine(line, status, state);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
buf += decoder.decode();
|
|
190
|
-
if (buf.trim()) {
|
|
191
|
-
handlePullLine(buf, status, state);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
type PullStreamState = { lastStatus: string; lastPct: number };
|
|
196
|
-
|
|
197
|
-
function handlePullLine(
|
|
198
|
-
line: string,
|
|
199
|
-
status: (line: string) => void,
|
|
200
|
-
state: PullStreamState,
|
|
201
|
-
): void {
|
|
202
|
-
const trimmed = line.trim();
|
|
203
|
-
if (!trimmed) return;
|
|
204
|
-
let row: {
|
|
205
|
-
status?: string;
|
|
206
|
-
error?: string;
|
|
207
|
-
completed?: number;
|
|
208
|
-
total?: number;
|
|
209
|
-
};
|
|
210
|
-
try {
|
|
211
|
-
row = JSON.parse(trimmed) as typeof row;
|
|
212
|
-
} catch {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (row.error) {
|
|
216
|
-
throw new OllamaPullError(`ollama pull: ${row.error}`);
|
|
217
|
-
}
|
|
218
|
-
const st = row.status?.trim() ?? "";
|
|
219
|
-
if (typeof row.completed === "number" && typeof row.total === "number" && row.total > 0) {
|
|
220
|
-
const pct = Math.min(100, Math.floor((100 * row.completed) / row.total));
|
|
221
|
-
if (pct !== state.lastPct && (pct === 100 || pct - state.lastPct >= 5)) {
|
|
222
|
-
state.lastPct = pct;
|
|
223
|
-
status(`oke: Ollama pull ${pct}%${st ? ` (${st})` : ""}`);
|
|
224
|
-
}
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (st && st !== state.lastStatus) {
|
|
228
|
-
state.lastStatus = st;
|
|
229
|
-
if (/^pulling\s+[a-f0-9]{12}/i.test(st)) return;
|
|
230
|
-
status(`oke: Ollama ${st}`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* llama.cpp image recipe — default local AI (`ghcr.io/ggml-org/llama.cpp`).
|
|
3
|
-
*
|
|
4
|
-
* OpenAI-compatible `llama-server` on 8080. Pin ≥ {@link LLAMA_CPP_MIN_SAFE_BUILD}
|
|
5
|
-
* (CVE-2026-27940 / CVE-2026-33298 floor). Host publish is loopback-only;
|
|
6
|
-
* never publish RPC. Models via Docker Hub `ai/` — do not load arbitrary
|
|
7
|
-
* untrusted GGUF files.
|
|
8
|
-
*
|
|
9
|
-
* b10290+ issues this recipe works around:
|
|
10
|
-
* 1. Bare `--docker-repo` / router `--models-preset` children re-enter empty
|
|
11
|
-
* router mode (`is_router_server` only checks `-m` / `-hf`) → forever
|
|
12
|
-
* `loading`.
|
|
13
|
-
* 2. `llama download -dr` only accepts `application/vnd.docker.ai.gguf.v3`;
|
|
14
|
-
* newer Hub models (e.g. gemma4) ship `vnd.cncf.model.weight.v1.raw`.
|
|
15
|
-
*
|
|
16
|
-
* Entrypoint: Hub-pull (native download, then CNCF-aware fallback) → serve
|
|
17
|
-
* single-model with `-m` + `--alias`.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import type { ImageRecipe } from "../types.ts";
|
|
21
|
-
|
|
22
|
-
/** Minimum safe llama.cpp build (GGUF parser CVEs through CVE-2026-27940). */
|
|
23
|
-
export const LLAMA_CPP_MIN_SAFE_BUILD = 8146;
|
|
24
|
-
|
|
25
|
-
/** Pinned default image — verified ≥ {@link LLAMA_CPP_MIN_SAFE_BUILD}; never `latest`. */
|
|
26
|
-
export const LLAMA_CPP_IMAGE = "ghcr.io/ggml-org/llama.cpp:server-b10450";
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Basename of the generated entrypoint.
|
|
30
|
-
*
|
|
31
|
-
* Lives under `.oke/` (gitignored generated artefacts), never beside app
|
|
32
|
-
* TypeScript — compose mounts it via {@link LLAMA_CPP_ENTRYPOINT_HOST_PATH}.
|
|
33
|
-
*/
|
|
34
|
-
export const LLAMA_CPP_ENTRYPOINT_FILE = "llama-entrypoint.py";
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Host path relative to the compose directory (`docker/`) for the generated
|
|
38
|
-
* entrypoint. Resolves to `<project>/.oke/llama-entrypoint.py`.
|
|
39
|
-
*/
|
|
40
|
-
export const LLAMA_CPP_ENTRYPOINT_HOST_PATH = `../.oke/${LLAMA_CPP_ENTRYPOINT_FILE}`;
|
|
41
|
-
|
|
42
|
-
/** In-container path for {@link LLAMA_CPP_ENTRYPOINT_FILE}. */
|
|
43
|
-
export const LLAMA_CPP_ENTRYPOINT_MOUNT = "/oke/llama-entrypoint.py";
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Python entrypoint (runs inside the llama.cpp image, which ships `python3`):
|
|
47
|
-
* pull curated Docker Hub `ai/` GGUF into the cache volume, then `exec`
|
|
48
|
-
* `llama-server -m <gguf> --alias <id>` (single-model, not router).
|
|
49
|
-
*
|
|
50
|
-
* Source of truth is this TypeScript string — the `.py` file is a generated
|
|
51
|
-
* runtime artefact under `.oke/`, not app code.
|
|
52
|
-
*/
|
|
53
|
-
export function buildLlamaCppEntrypoint(): string {
|
|
54
|
-
return `#!/usr/bin/env python3
|
|
55
|
-
# Generated by oke — do not edit. Source: okengine docker llama.cpp recipe.
|
|
56
|
-
"""OKE llama.cpp entrypoint — Hub-pull curated ai/ model, then serve single-model."""
|
|
57
|
-
|
|
58
|
-
from __future__ import annotations
|
|
59
|
-
|
|
60
|
-
import json
|
|
61
|
-
import os
|
|
62
|
-
import subprocess
|
|
63
|
-
import sys
|
|
64
|
-
import urllib.error
|
|
65
|
-
import urllib.parse
|
|
66
|
-
import urllib.request
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def cache_dir() -> str:
|
|
70
|
-
return os.environ.get("LLAMA_CACHE", "/root/.cache/llama.cpp")
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def parse_model(model: str) -> tuple[str, str]:
|
|
74
|
-
raw = model.strip() or "granite3.3:2b"
|
|
75
|
-
if "/" not in raw.split(":", 1)[0]:
|
|
76
|
-
raw = f"ai/{raw}"
|
|
77
|
-
if ":" in raw:
|
|
78
|
-
repo, tag = raw.rsplit(":", 1)
|
|
79
|
-
else:
|
|
80
|
-
repo, tag = raw, "latest"
|
|
81
|
-
return repo, tag
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def expected_gguf(repo: str, tag: str) -> str:
|
|
85
|
-
return f"{repo.replace('/', '_')}_{tag}.gguf"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def existing_gguf(path: str) -> str | None:
|
|
89
|
-
if os.path.isfile(path) and os.path.getsize(path) > 0:
|
|
90
|
-
return path
|
|
91
|
-
return None
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def llama_download(model: str) -> int:
|
|
95
|
-
return subprocess.call(
|
|
96
|
-
["/app/llama", "download", "-dr", model, "--no-mmproj"],
|
|
97
|
-
stdout=sys.stdout,
|
|
98
|
-
stderr=sys.stderr,
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def registry_token(repo: str) -> str:
|
|
103
|
-
scope = urllib.parse.quote(f"repository:{repo}:pull", safe="")
|
|
104
|
-
url = f"https://auth.docker.io/token?service=registry.docker.io&scope={scope}"
|
|
105
|
-
with urllib.request.urlopen(url, timeout=60) as res:
|
|
106
|
-
return json.load(res)["token"]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def fetch_manifest(repo: str, tag: str, token: str) -> dict:
|
|
110
|
-
req = urllib.request.Request(
|
|
111
|
-
f"https://registry-1.docker.io/v2/{repo}/manifests/{tag}",
|
|
112
|
-
headers={
|
|
113
|
-
"Authorization": f"Bearer {token}",
|
|
114
|
-
"Accept": (
|
|
115
|
-
"application/vnd.oci.image.manifest.v1+json,"
|
|
116
|
-
"application/vnd.docker.distribution.manifest.v2+json,"
|
|
117
|
-
"application/vnd.oci.image.index.v1+json,"
|
|
118
|
-
"application/vnd.docker.distribution.manifest.list.v2+json"
|
|
119
|
-
),
|
|
120
|
-
},
|
|
121
|
-
)
|
|
122
|
-
with urllib.request.urlopen(req, timeout=60) as res:
|
|
123
|
-
return json.load(res)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def pick_gguf_layer(manifest: dict) -> tuple[str, str]:
|
|
127
|
-
"""Return (digest, suggested_filename) for the primary GGUF weight layer."""
|
|
128
|
-
layers = manifest.get("layers") or []
|
|
129
|
-
candidates: list[tuple[str, str, int]] = []
|
|
130
|
-
for layer in layers:
|
|
131
|
-
media = str(layer.get("mediaType") or "")
|
|
132
|
-
digest = str(layer.get("digest") or "")
|
|
133
|
-
ann = layer.get("annotations") or {}
|
|
134
|
-
filepath = str(ann.get("org.cncf.model.filepath") or "")
|
|
135
|
-
name = filepath.split("/")[-1] if filepath else ""
|
|
136
|
-
is_gguf = (
|
|
137
|
-
media == "application/vnd.docker.ai.gguf.v3"
|
|
138
|
-
or "gguf" in media.lower()
|
|
139
|
-
or name.lower().endswith(".gguf")
|
|
140
|
-
)
|
|
141
|
-
if not is_gguf or not digest:
|
|
142
|
-
continue
|
|
143
|
-
if name.lower().startswith("mmproj"):
|
|
144
|
-
continue
|
|
145
|
-
size = int(layer.get("size") or 0)
|
|
146
|
-
candidates.append((digest, name or "model.gguf", size))
|
|
147
|
-
if not candidates:
|
|
148
|
-
raise RuntimeError("No GGUF weight layer in Docker / CNCF model manifest")
|
|
149
|
-
# Prefer the largest non-mmproj GGUF (main weights vs projectors).
|
|
150
|
-
candidates.sort(key=lambda c: c[2], reverse=True)
|
|
151
|
-
digest, name, _ = candidates[0]
|
|
152
|
-
return digest, name
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def download_blob(repo: str, digest: str, token: str, dest: str) -> None:
|
|
156
|
-
url = f"https://registry-1.docker.io/v2/{repo}/blobs/{digest}"
|
|
157
|
-
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
|
158
|
-
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
|
159
|
-
partial = f"{dest}.partial"
|
|
160
|
-
print(f"oke ai: downloading {digest} → {dest}", flush=True)
|
|
161
|
-
with urllib.request.urlopen(req, timeout=600) as res, open(partial, "wb") as out:
|
|
162
|
-
total = int(res.headers.get("Content-Length") or 0)
|
|
163
|
-
done = 0
|
|
164
|
-
last_pct = -1
|
|
165
|
-
while True:
|
|
166
|
-
chunk = res.read(1024 * 1024)
|
|
167
|
-
if not chunk:
|
|
168
|
-
break
|
|
169
|
-
out.write(chunk)
|
|
170
|
-
done += len(chunk)
|
|
171
|
-
if total:
|
|
172
|
-
pct = (100 * done) // total
|
|
173
|
-
if pct >= last_pct + 5 or done == total:
|
|
174
|
-
last_pct = pct
|
|
175
|
-
print(
|
|
176
|
-
f"oke ai: download {pct}% ({done // (1024 * 1024)} / {total // (1024 * 1024)} MiB)",
|
|
177
|
-
flush=True,
|
|
178
|
-
)
|
|
179
|
-
os.replace(partial, dest)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def hub_pull(repo: str, tag: str, dest: str) -> None:
|
|
183
|
-
token = registry_token(repo)
|
|
184
|
-
manifest = fetch_manifest(repo, tag, token)
|
|
185
|
-
# Index → pick first linux manifest if present.
|
|
186
|
-
if "manifests" in manifest:
|
|
187
|
-
digests = [m.get("digest") for m in manifest["manifests"] if m.get("digest")]
|
|
188
|
-
if not digests:
|
|
189
|
-
raise RuntimeError("Empty OCI index for model")
|
|
190
|
-
req = urllib.request.Request(
|
|
191
|
-
f"https://registry-1.docker.io/v2/{repo}/manifests/{digests[0]}",
|
|
192
|
-
headers={
|
|
193
|
-
"Authorization": f"Bearer {token}",
|
|
194
|
-
"Accept": "application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.v2+json",
|
|
195
|
-
},
|
|
196
|
-
)
|
|
197
|
-
with urllib.request.urlopen(req, timeout=60) as res:
|
|
198
|
-
manifest = json.load(res)
|
|
199
|
-
digest, _name = pick_gguf_layer(manifest)
|
|
200
|
-
download_blob(repo, digest, token, dest)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def ensure_model(model: str) -> str:
|
|
204
|
-
repo, tag = parse_model(model)
|
|
205
|
-
# llama download -dr expects id without forced ai/ prefix when using default org.
|
|
206
|
-
dr = model.strip() or "granite3.3:2b"
|
|
207
|
-
if dr.startswith("ai/"):
|
|
208
|
-
dr = dr[3:]
|
|
209
|
-
dest = os.path.join(cache_dir(), expected_gguf(repo, tag))
|
|
210
|
-
hit = existing_gguf(dest)
|
|
211
|
-
if hit:
|
|
212
|
-
print(f"oke ai: using cached {hit}", flush=True)
|
|
213
|
-
return hit
|
|
214
|
-
|
|
215
|
-
print(f"oke ai: ensuring Docker Hub model '{dr}' is cached…", flush=True)
|
|
216
|
-
rc = llama_download(dr)
|
|
217
|
-
hit = existing_gguf(dest)
|
|
218
|
-
if hit:
|
|
219
|
-
return hit
|
|
220
|
-
# Native downloader may write the same basename; also accept exact path only.
|
|
221
|
-
print(
|
|
222
|
-
f"oke ai: native download exit {rc}; trying CNCF / Hub registry pull…",
|
|
223
|
-
flush=True,
|
|
224
|
-
)
|
|
225
|
-
try:
|
|
226
|
-
hub_pull(repo, tag, dest)
|
|
227
|
-
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, RuntimeError, KeyError, ValueError) as err:
|
|
228
|
-
print(f"oke ai: failed to download model '{dr}': {err}", file=sys.stderr, flush=True)
|
|
229
|
-
sys.exit(1)
|
|
230
|
-
hit = existing_gguf(dest)
|
|
231
|
-
if not hit:
|
|
232
|
-
print(f"oke ai: download produced no file at {dest}", file=sys.stderr, flush=True)
|
|
233
|
-
sys.exit(1)
|
|
234
|
-
return hit
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def main() -> None:
|
|
238
|
-
model = (os.environ.get("OKE_AI_MODEL") or "granite3.3:2b").strip() or "granite3.3:2b"
|
|
239
|
-
gguf = ensure_model(model)
|
|
240
|
-
# Without an explicit --ctx-size, llama-server defaults to the model's
|
|
241
|
-
# full native training context (32K-256K+ for many current models). The
|
|
242
|
-
# KV cache for that is allocated up front and can be many times the
|
|
243
|
-
# model's own file size, OOM-killing the container even with generous
|
|
244
|
-
# Docker memory — independent of how small the model itself is.
|
|
245
|
-
# OKE_AI_CTX_SIZE (.env.local) overrides this default.
|
|
246
|
-
ctx_size = (os.environ.get("OKE_AI_CTX_SIZE") or "4096").strip() or "4096"
|
|
247
|
-
print(f"oke ai: serving {model} from {gguf} (ctx={ctx_size})", flush=True)
|
|
248
|
-
os.execv(
|
|
249
|
-
"/app/llama-server",
|
|
250
|
-
[
|
|
251
|
-
"/app/llama-server",
|
|
252
|
-
"--host",
|
|
253
|
-
"0.0.0.0",
|
|
254
|
-
"--port",
|
|
255
|
-
"8080",
|
|
256
|
-
"--model",
|
|
257
|
-
gguf,
|
|
258
|
-
"--alias",
|
|
259
|
-
model,
|
|
260
|
-
"--ctx-size",
|
|
261
|
-
ctx_size,
|
|
262
|
-
],
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if __name__ == "__main__":
|
|
267
|
-
main()
|
|
268
|
-
`;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/** llama-server — OpenAI-compatible HTTP on 8080; loopback publish only. */
|
|
272
|
-
export const llamaCpp: ImageRecipe = {
|
|
273
|
-
id: "llama-cpp",
|
|
274
|
-
port: 8080,
|
|
275
|
-
match: (i) => /llama\.cpp|llamacpp/i.test(i),
|
|
276
|
-
apply: (s) => ({
|
|
277
|
-
// OKE_AI_MODEL comes from compose `env_file` (`.env.local`). Do not
|
|
278
|
-
// re-declare it as `${OKE_AI_MODEL:-granite3.3:2b}` here — Compose interpolates
|
|
279
|
-
// that from the *host* shell and can override the stack file with the
|
|
280
|
-
// default. Never set LLAMA_ARG_MODELS_PRESET / LLAMA_ARG_DOCKER_REPO
|
|
281
|
-
// (router children inherit `LLAMA_ARG_*` and can OOM).
|
|
282
|
-
entrypoint: ["/usr/bin/python3", LLAMA_CPP_ENTRYPOINT_MOUNT],
|
|
283
|
-
volumes: [
|
|
284
|
-
`${s.serviceName}-models:/root/.cache`,
|
|
285
|
-
`${LLAMA_CPP_ENTRYPOINT_HOST_PATH}:${LLAMA_CPP_ENTRYPOINT_MOUNT}:ro`,
|
|
286
|
-
],
|
|
287
|
-
publishBind: "127.0.0.1",
|
|
288
|
-
healthcheck: {
|
|
289
|
-
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/health >/dev/null || exit 1"],
|
|
290
|
-
interval: "5s",
|
|
291
|
-
timeout: "5s",
|
|
292
|
-
retries: 24,
|
|
293
|
-
// First boot may download a multi‑GB Hub model (CNCF pull) into cache.
|
|
294
|
-
start_period: "900s",
|
|
295
|
-
},
|
|
296
|
-
}),
|
|
297
|
-
url: (_s, c) => `http://${c.host}:${c.port}/v1`,
|
|
298
|
-
};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ollama image recipe — local model server (`ollama/ollama`).
|
|
3
|
-
*
|
|
4
|
-
* Fully supported alternative to the default llama.cpp recipe. Pin ≥
|
|
5
|
-
* {@link OLLAMA_MIN_SAFE_VERSION} (CVE-2026-7482 floor). Host publish is
|
|
6
|
-
* loopback-only — never expose `:11434` on `0.0.0.0`. Load models only from
|
|
7
|
-
* Ollama's library; do not feed arbitrary untrusted GGUF into `/api/create`.
|
|
8
|
-
*
|
|
9
|
-
* Model pull is **not** done here: after compose is up,
|
|
10
|
-
* {@link ensureOllamaModel} POSTs `/api/pull` to this container's loopback URL.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { ImageRecipe } from "../types.ts";
|
|
14
|
-
|
|
15
|
-
/** Minimum safe Ollama release (CVE-2026-7482 — own Go GGUF loader). */
|
|
16
|
-
export const OLLAMA_MIN_SAFE_VERSION = "0.17.1";
|
|
17
|
-
|
|
18
|
-
/** Pinned default Ollama image — verified ≥ {@link OLLAMA_MIN_SAFE_VERSION}; never `latest`. */
|
|
19
|
-
export const OLLAMA_IMAGE = "ollama/ollama:0.32.13";
|
|
20
|
-
|
|
21
|
-
/** Ollama local model server. API on 11434; loopback publish only. */
|
|
22
|
-
export const ollama: ImageRecipe = {
|
|
23
|
-
id: "ollama",
|
|
24
|
-
port: 11434,
|
|
25
|
-
match: (i) => /(?:^|\/)ollama(?:[:@/]|$)/i.test(i),
|
|
26
|
-
apply: (s) => ({
|
|
27
|
-
environment: {
|
|
28
|
-
// Listen on all interfaces *inside* the container (Docker networking).
|
|
29
|
-
// Host publish uses publishBind — never 0.0.0.0 on the host.
|
|
30
|
-
OLLAMA_HOST: "0.0.0.0:11434",
|
|
31
|
-
OKE_AI_MODEL: "${OKE_AI_MODEL:-qwen3.5:9b}",
|
|
32
|
-
},
|
|
33
|
-
volumes: [`${s.serviceName}-data:/root/.ollama`],
|
|
34
|
-
publishBind: "127.0.0.1",
|
|
35
|
-
healthcheck: {
|
|
36
|
-
test: ["CMD-SHELL", "/bin/ollama list >/dev/null 2>&1 || exit 1"],
|
|
37
|
-
interval: "5s",
|
|
38
|
-
timeout: "5s",
|
|
39
|
-
retries: 24,
|
|
40
|
-
start_period: "10s",
|
|
41
|
-
},
|
|
42
|
-
}),
|
|
43
|
-
url: (_s, c) => `http://${c.host}:${c.port}`,
|
|
44
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SGLang image recipe — production-tier structured/agent inference.
|
|
3
|
-
*
|
|
4
|
-
* Prefer a managed provider unless you run inference yourself. Use SGLang when
|
|
5
|
-
* the workload is structured generation / agent-heavy on real GPU hardware;
|
|
6
|
-
* use vLLM for general multi-user serving. Pin an explicit tag — never
|
|
7
|
-
* `latest`. Host publish is loopback-only.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ImageRecipe } from "../types.ts";
|
|
11
|
-
|
|
12
|
-
/** Pinned SGLang runtime image — never `latest`. */
|
|
13
|
-
export const SGLANG_IMAGE = "lmsysorg/sglang:v0.5.17-runtime";
|
|
14
|
-
|
|
15
|
-
/** SGLang — OpenAI-compatible HTTP on 30000; GPU reservation; loopback publish. */
|
|
16
|
-
export const sglang: ImageRecipe = {
|
|
17
|
-
id: "sglang",
|
|
18
|
-
port: 30000,
|
|
19
|
-
match: (i) => /sglang/i.test(i),
|
|
20
|
-
apply: (s) => ({
|
|
21
|
-
command: [
|
|
22
|
-
"python3",
|
|
23
|
-
"-m",
|
|
24
|
-
"sglang.launch_server",
|
|
25
|
-
"--model-path",
|
|
26
|
-
"${OKE_AI_MODEL:-Qwen/Qwen3-0.6B}",
|
|
27
|
-
"--host",
|
|
28
|
-
"0.0.0.0",
|
|
29
|
-
"--port",
|
|
30
|
-
"30000",
|
|
31
|
-
],
|
|
32
|
-
environment: {
|
|
33
|
-
OKE_AI_MODEL: "${OKE_AI_MODEL:-Qwen/Qwen3-0.6B}",
|
|
34
|
-
HF_HOME: "/root/.cache/huggingface",
|
|
35
|
-
},
|
|
36
|
-
volumes: [`${s.serviceName}-models:/root/.cache/huggingface`],
|
|
37
|
-
ipc: "host",
|
|
38
|
-
publishBind: "127.0.0.1",
|
|
39
|
-
deploy: {
|
|
40
|
-
resources: {
|
|
41
|
-
reservations: {
|
|
42
|
-
devices: [{ driver: "nvidia", count: "all", capabilities: ["gpu"] }],
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
healthcheck: {
|
|
47
|
-
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:30000/health >/dev/null || exit 1"],
|
|
48
|
-
interval: "10s",
|
|
49
|
-
timeout: "5s",
|
|
50
|
-
retries: 30,
|
|
51
|
-
start_period: "120s",
|
|
52
|
-
},
|
|
53
|
-
}),
|
|
54
|
-
url: (_s, c) => `http://${c.host}:${c.port}/v1`,
|
|
55
|
-
};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vLLM image recipe — production-tier local inference (`vllm/vllm-openai`).
|
|
3
|
-
*
|
|
4
|
-
* OpenAI-compatible server for multi-user GPU concurrency. Prefer a managed
|
|
5
|
-
* provider unless you run inference yourself. Pin an explicit tag — never
|
|
6
|
-
* `latest`. Host publish is loopback-only (same hard rule as llama.cpp).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { ImageRecipe } from "../types.ts";
|
|
10
|
-
|
|
11
|
-
/** Pinned vLLM OpenAI server image — never `latest`. */
|
|
12
|
-
export const VLLM_IMAGE = "vllm/vllm-openai:v0.27.1";
|
|
13
|
-
|
|
14
|
-
/** vLLM — OpenAI-compatible HTTP on 8000; GPU reservation; loopback publish. */
|
|
15
|
-
export const vllm: ImageRecipe = {
|
|
16
|
-
id: "vllm",
|
|
17
|
-
port: 8000,
|
|
18
|
-
match: (i) => /(?:^|\/)vllm(?:[:@/]|$)/i.test(i),
|
|
19
|
-
apply: (s) => ({
|
|
20
|
-
command: ["--model", "${OKE_AI_MODEL:-Qwen/Qwen3-0.6B}", "--host", "0.0.0.0", "--port", "8000"],
|
|
21
|
-
environment: {
|
|
22
|
-
OKE_AI_MODEL: "${OKE_AI_MODEL:-Qwen/Qwen3-0.6B}",
|
|
23
|
-
HF_HOME: "/root/.cache/huggingface",
|
|
24
|
-
},
|
|
25
|
-
volumes: [`${s.serviceName}-models:/root/.cache/huggingface`],
|
|
26
|
-
ipc: "host",
|
|
27
|
-
publishBind: "127.0.0.1",
|
|
28
|
-
deploy: {
|
|
29
|
-
resources: {
|
|
30
|
-
reservations: {
|
|
31
|
-
devices: [{ driver: "nvidia", count: "all", capabilities: ["gpu"] }],
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
healthcheck: {
|
|
36
|
-
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8000/health >/dev/null || exit 1"],
|
|
37
|
-
interval: "10s",
|
|
38
|
-
timeout: "5s",
|
|
39
|
-
retries: 30,
|
|
40
|
-
start_period: "120s",
|
|
41
|
-
},
|
|
42
|
-
}),
|
|
43
|
-
url: (_s, c) => `http://${c.host}:${c.port}/v1`,
|
|
44
|
-
};
|