okengine 0.21.1 → 0.23.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 +11 -4
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +15 -9
- package/manifest.v1.schema.json +54 -0
- package/package.json +53 -38
- package/site/content/docs/ai/mcp.mdx +13 -9
- package/site/content/docs/ai/skills.mdx +2 -2
- package/site/content/docs/client/calling.mdx +27 -19
- package/site/content/docs/client/index.mdx +1 -1
- package/site/content/docs/elements/ai/agents.mdx +61 -13
- package/site/content/docs/elements/ai/decide.mdx +536 -0
- package/site/content/docs/elements/ai/events.mdx +373 -0
- package/site/content/docs/elements/ai/index.mdx +9 -3
- package/site/content/docs/elements/ai/meta.json +1 -1
- package/site/content/docs/elements/ai/prompts.mdx +4 -2
- package/site/content/docs/elements/channel/email.mdx +1 -1
- package/site/content/docs/elements/channel/index.mdx +1 -1
- package/site/content/docs/elements/flow/http.mdx +4 -4
- package/site/content/docs/elements/flow/index.mdx +3 -3
- package/site/content/docs/elements/flow/meta.json +1 -1
- package/site/content/docs/elements/gate/auth.mdx +4 -1
- package/site/content/docs/elements/gate/tenancy.mdx +1 -1
- package/site/content/docs/elements/store/index.mdx +1 -1
- package/site/content/docs/elements/store/kv.mdx +5 -5
- package/site/content/docs/elements/vault/rotation.mdx +15 -0
- package/site/content/docs/plugins/otp.mdx +31 -31
- package/site/content/docs/recipes/dragonfly.mdx +5 -5
- package/site/content/docs/recipes/index.mdx +6 -6
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +1 -1
- package/site/content/docs/recipes/postgres.mdx +12 -8
- package/site/content/docs/recipes/redis.mdx +4 -4
- package/site/content/docs/reference/cli.mdx +2 -1
- package/site/content/docs/reference/configuration.mdx +11 -9
- package/site/content/docs/reference/errors.mdx +12 -2
- package/site/content/docs/reference/fx.mdx +35 -21
- package/site/content/docs/reference/idempotency.mdx +192 -0
- package/site/content/docs/reference/index.mdx +6 -1
- package/site/content/docs/reference/meta.json +2 -1
- package/site/content/docs/understand/meta.json +1 -1
- package/site/content/docs/{elements/flow → understand}/routing.mdx +11 -10
- package/site/content/docs/understand/the-architecture.mdx +14 -12
- package/site/content/docs/understand/try-it.mdx +3 -3
- package/src/auth/api-key-sql.test.ts +44 -0
- package/src/auth/api-key-sql.ts +1 -0
- package/src/auth/api-keys.ts +30 -12
- package/src/auth/config.ts +6 -2
- package/src/auth/gate-auth.test.ts +20 -0
- package/src/cli/agents-md.test.ts +25 -0
- package/src/cli/ask-vault-gaps.test.ts +38 -1
- package/src/cli/ask-vault-gaps.ts +35 -2
- package/src/cli/competitor-mention-removal.test.ts +3 -3
- package/src/cli/decide.ts +174 -0
- package/src/cli/decision-lock-watch.test.ts +45 -0
- package/src/cli/decision-lock-watch.ts +51 -0
- package/src/cli/dev-app-runner.ts +34 -8
- package/src/cli/dev-auth-secret.test.ts +49 -0
- package/src/cli/dev-auth-secret.ts +49 -0
- package/src/cli/dev-hot.test.ts +41 -0
- package/src/cli/dev-hot.ts +48 -0
- package/src/cli/dev.test.ts +1 -1
- package/src/cli/dev.ts +24 -6
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/eval.ts +142 -1
- package/src/cli/index.ts +14 -1
- package/src/cli/load-config.images.test.ts +12 -12
- package/src/cli/load-config.ts +2 -2
- package/src/cli/mcp-from-console.ts +48 -0
- package/src/cli/registry.ts +35 -1
- package/src/cli/vault-cmd.test.ts +23 -0
- package/src/cli/vault-cmd.ts +7 -0
- package/src/client/agent.ts +307 -0
- package/src/client/create.ts +18 -1
- package/src/client/transport.test.ts +245 -0
- package/src/client/transport.ts +133 -19
- package/src/client/types.ts +29 -3
- package/src/client-react/index.ts +7 -1
- package/src/client-react/use-agent-run.test.ts +271 -0
- package/src/client-react/use-agent-run.ts +193 -0
- package/src/compiler/ai-approval.test.ts +36 -0
- package/src/compiler/ai-repair.test.ts +22 -0
- package/src/compiler/decisions.extract.test.ts +164 -0
- package/src/compiler/effects-infer.ts +753 -17
- package/src/compiler/extract.test.ts +37 -0
- package/src/compiler/extract.ts +381 -24
- package/src/compiler/fixtures/skyport.expected.json +24 -0
- package/src/compiler/fx-follow.test.ts +164 -0
- package/src/compiler/fx-index.ts +399 -0
- package/src/compiler/response.ts +35 -2
- package/src/config/index.ts +21 -3
- package/src/console/server/ai-runs-flows.ts +269 -0
- package/src/console/server/ai-runs.test.ts +323 -0
- package/src/console/server/ai-runs.ts +562 -0
- package/src/console/server/ai.ts +12 -1
- package/src/console/server/app.ts +10 -1
- package/src/console/server/decisions-flows.ts +157 -0
- package/src/console/server/decisions.test.ts +66 -0
- package/src/console/server/decisions.ts +229 -0
- package/src/console/server/flows-invoke.test.ts +2 -2
- package/src/console/server/flows.ts +50 -2
- package/src/console/server/invoke-user-flow.ts +9 -0
- package/src/console/server/runs-ingest.ts +16 -0
- package/src/console/server/serve.ts +5 -0
- package/src/console/server/state.ts +25 -1
- package/src/console/server/store-stats.test.ts +47 -0
- package/src/console/server/store-stats.ts +92 -18
- package/src/console/ui-next/dist/assets/access-page-DDKkhT9v.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-62Xts8Xi.js +1 -0
- package/src/console/ui-next/dist/assets/{cache-glyph-92uM5MO7.js → cache-glyph-DyeoKHKA.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-DtGVPtcs.js → call-pii-button-DvfpLXsU.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-DN7l6zmC.js → collapsible-BY03SeCg.js} +1 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
- package/src/console/ui-next/dist/assets/decisions-page-CgkKlA56.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Y6HLjyJ5.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-DLPfNy-_.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-BlAEVgNW.js → highlighted-json-D7nNzpgB.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-BDf7OAHv.js → http-method-DdL19zzh.js} +1 -1
- package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
- package/src/console/ui-next/dist/assets/index-C0lc_s9d.js +58 -0
- package/src/console/ui-next/dist/assets/observability-page-CRlcyLCC.js +8 -0
- package/src/console/ui-next/dist/assets/{replica-lag-B3GLNVfF.js → replica-lag-Bb3FWUu9.js} +7 -7
- package/src/console/ui-next/dist/assets/request-meta-DYBMWhX8.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-CcE-SXC8.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CKMSEQ4s.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-QOfk0M0H.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-DKC1CDDd.js +1 -0
- package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-B54v4Yit.js +2 -0
- package/src/console/ui-next/dist/index.html +4 -3
- package/src/console/ui-next/seed-parked-approval.ts +126 -0
- package/src/console/ui-next/src/client.ts +224 -1
- package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
- package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
- package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
- package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
- package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
- package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
- package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
- package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
- package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
- package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
- package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
- package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
- package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
- package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +15 -2
- package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
- package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
- package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
- package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
- package/src/console/ui-next/src/router.tsx +17 -0
- package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
- package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
- package/src/console/ui-next/vite.config.ts +9 -0
- package/src/docker/docker.test.ts +13 -13
- package/src/docker/images-config.test.ts +12 -12
- package/src/docker/recipes/dragonfly.ts +3 -2
- package/src/docker/recipes/redis.ts +1 -1
- package/src/docker/stack-id.test.ts +1 -1
- package/src/drivers/ai-anthropic.ts +212 -21
- package/src/drivers/ai-mock.ts +34 -1
- package/src/drivers/ai-openai-compatible.ts +67 -26
- package/src/drivers/ai-preconnect.ts +18 -0
- package/src/drivers/ai-stream.test.ts +62 -0
- package/src/drivers/ai-types.ts +20 -0
- package/src/drivers/journal-postgres.ts +455 -3
- package/src/drivers/meilisearch.ts +2 -0
- package/src/drivers/memory-ddl.test.ts +18 -0
- package/src/drivers/memory.ts +21 -1
- package/src/drivers/pg-rls.test.ts +31 -0
- package/src/drivers/pg-rls.ts +15 -0
- package/src/drivers/postgres.test.ts +9 -0
- package/src/drivers/postgres.ts +49 -11
- package/src/drivers/vault-builtin.test.ts +10 -0
- package/src/drivers/vault-builtin.ts +13 -1
- package/src/drivers/vault-remote-bag.ts +5 -1
- package/src/drivers/vault-types.ts +6 -1
- package/src/elements/ai/agent-event-slot.ts +27 -0
- package/src/elements/ai/approval-http.ts +207 -0
- package/src/elements/ai/approval.test.ts +741 -0
- package/src/elements/ai/approval.ts +228 -0
- package/src/elements/ai/decisions/bind.ts +163 -0
- package/src/elements/ai/decisions/certificate.test.ts +291 -0
- package/src/elements/ai/decisions/certificate.ts +377 -0
- package/src/elements/ai/decisions/certify.ts +314 -0
- package/src/elements/ai/decisions/decide.test.ts +953 -0
- package/src/elements/ai/decisions/e2e.test.ts +399 -0
- package/src/elements/ai/decisions/export.test.ts +120 -0
- package/src/elements/ai/decisions/export.ts +146 -0
- package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
- package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
- package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
- package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
- package/src/elements/ai/decisions/http.test.ts +154 -0
- package/src/elements/ai/decisions/http.ts +240 -0
- package/src/elements/ai/decisions/labels.test.ts +282 -0
- package/src/elements/ai/decisions/labels.ts +257 -0
- package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
- package/src/elements/ai/decisions/openrouter.ts +36 -0
- package/src/elements/ai/decisions/provider.ts +102 -0
- package/src/elements/ai/decisions/typesafe.ts +36 -0
- package/src/elements/ai/declare.ts +252 -1
- package/src/elements/ai/events.test.ts +379 -0
- package/src/elements/ai/events.ts +138 -0
- package/src/elements/ai/run-events.test.ts +461 -0
- package/src/elements/ai/run-events.ts +522 -0
- package/src/elements/ai/runtime.ts +1186 -105
- package/src/elements/ai/stream-turn.ts +214 -0
- package/src/elements/ai/stream.live.test.ts +76 -0
- package/src/elements/ai/subagent.test.ts +136 -0
- package/src/elements/ai.test.ts +497 -3
- package/src/elements/ai.ts +16 -1
- package/src/elements/clock/durable.ts +27 -1
- package/src/elements/store/emit-drizzle.ts +2 -2
- package/src/elements/store/live-default.test.ts +2 -0
- package/src/elements/store/live-http.test.ts +1 -0
- package/src/elements/store/resource-list-docs.test.ts +2 -2
- package/src/elements/store/resource.test.ts +3 -3
- package/src/elements/store/schema-decl.test.ts +1 -0
- package/src/elements/store/sql-condition.ts +24 -7
- package/src/elements/store/sql-session.test.ts +51 -0
- package/src/elements/store/sql-session.ts +111 -1
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/vault/audit.test.ts +137 -0
- package/src/elements/vault/audit.ts +106 -13
- package/src/elements/vault/boot-chain.ts +9 -1
- package/src/elements/vault/builtin-adapter.ts +33 -3
- package/src/i18n/catalogs/ar.ts +9 -0
- package/src/i18n/catalogs/en.ts +9 -0
- package/src/index.ts +2 -0
- package/src/kernel/abort-scope.ts +33 -2
- package/src/kernel/agent-event-store.ts +448 -0
- package/src/kernel/app.ts +294 -19
- package/src/kernel/auto-cache.test.ts +6 -6
- package/src/kernel/boot-bind/ai.ts +3 -0
- package/src/kernel/boot-bind/store.test.ts +26 -0
- package/src/kernel/boot-bind/store.ts +82 -6
- package/src/kernel/boot-bind/vault.ts +1 -0
- package/src/kernel/boot.ts +34 -5
- package/src/kernel/builtin-errors.ts +10 -0
- package/src/kernel/capability.ts +3 -0
- package/src/kernel/client-descriptor.ts +1 -0
- package/src/kernel/concurrency.test.ts +33 -0
- package/src/kernel/decision-label-store.ts +441 -0
- package/src/kernel/effects-stamping.test.ts +2 -2
- package/src/kernel/effects.test.ts +2 -1
- package/src/kernel/effects.ts +13 -1
- package/src/kernel/element-registries.ts +3 -0
- package/src/kernel/errors-compiler.ts +20 -0
- package/src/kernel/errors-text.ts +4 -0
- package/src/kernel/errors.ts +2 -0
- package/src/kernel/external-effects.test.ts +36 -0
- package/src/kernel/flow.test.ts +2 -3
- package/src/kernel/flow.ts +24 -0
- package/src/kernel/fx-decide.ts +761 -0
- package/src/kernel/fx-fetch.ts +5 -1
- package/src/kernel/fx.test.ts +12 -5
- package/src/kernel/fx.ts +316 -29
- package/src/kernel/http-frame.test.ts +94 -0
- package/src/kernel/http-frame.ts +203 -0
- package/src/kernel/idempotency-store.ts +578 -0
- package/src/kernel/idempotency.test.ts +556 -0
- package/src/kernel/idempotency.ts +304 -0
- package/src/kernel/index.ts +1 -0
- package/src/kernel/journal.ts +160 -0
- package/src/kernel/json-result.ts +10 -0
- package/src/kernel/pipeline.test.ts +45 -0
- package/src/kernel/sse-id.ts +32 -0
- package/src/manifest/diff.ts +1 -0
- package/src/manifest/types.ts +37 -2
- package/src/mcp/ai-tools.test.ts +202 -0
- package/src/mcp/authorization.ts +66 -0
- package/src/mcp/session.ts +3 -0
- package/src/mcp/tools.ts +168 -0
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/otp.test.ts +65 -1
- package/src/plugins/otp.ts +59 -7
- package/src/release/absolute-regression.test.ts +63 -0
- package/src/release/http-graph.test.ts +27 -0
- package/src/release/http-graph.ts +95 -0
- package/src/release/index.ts +3 -0
- package/src/release/limits.ts +17 -2
- package/src/release/measure.ts +144 -28
- package/src/runs/collect.ts +4 -1
- package/src/runs/parquet.test.ts +27 -1
- package/src/runs/parquet.ts +19 -0
- package/src/runs/types.ts +31 -0
- package/src/runtime/json-code-block.test.ts +106 -47
- package/src/runtime/json-code-block.ts +1209 -46
- package/src/term.test.ts +13 -0
- package/src/term.ts +81 -49
- package/src/test/create-test-app.test.ts +3 -1
- package/src/test/create-test-app.ts +40 -0
- package/src/test/live-signals.test.ts +1 -0
- package/src/test/provisions.integration.test.ts +1 -0
- package/src/test/tenant-isolation.test.ts +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
- package/src/console/ui-next/dist/assets/access-page-Bgt9bq2r.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-B43CXZZR.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-BmIR9FV8.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-BMHs-IzK.js +0 -1
- package/src/console/ui-next/dist/assets/index-BKpaes3n.js +0 -63
- package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
- package/src/console/ui-next/dist/assets/observability-page-WnVLI-0j.js +0 -4
- package/src/console/ui-next/dist/assets/request-meta-DMbnAe3f.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-KvFDingJ.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-Htk8Cm9t.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CFMPWX4f.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-C4NdNuxP.js +0 -1
- package/src/console/ui-next/dist/assets/use-vault-list-CT4-gajj.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-CBYl0LW_.js +0 -2
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Events"
|
|
3
|
+
description: "Stream an agent run to a chat UI as AG-UI events over server-sent events."
|
|
4
|
+
icon: "Radio"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
An agent stream is the same tool loop as `fx.run`, delivered while the model is still working. A support chat shows tokens, tool calls, and a finish reason as server-sent events.
|
|
9
|
+
|
|
10
|
+
Return `fx.json.stream` from the HTTP Flow. Parse the frames with `okengine/client/agent`.
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">
|
|
13
|
+
`return fx.json.stream(fx.run(agent, input, { stream: true }))`. `stream` is the third argument.
|
|
14
|
+
`fx.stream` yields plain text from a model, not these events.
|
|
15
|
+
</Callout>
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
<Steps>
|
|
20
|
+
|
|
21
|
+
<Step>
|
|
22
|
+
### Declare the agent
|
|
23
|
+
|
|
24
|
+
```typescript title="src/core/ai.ts"
|
|
25
|
+
import { ai } from "okengine";
|
|
26
|
+
|
|
27
|
+
export const smart = ai.model("smart", {
|
|
28
|
+
provider: "openrouter",
|
|
29
|
+
model: "openrouter/free",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const support = ai.agent("support", {
|
|
33
|
+
model: smart,
|
|
34
|
+
tools: ["orders.get"],
|
|
35
|
+
maxSteps: 4,
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
</Step>
|
|
40
|
+
|
|
41
|
+
<Step>
|
|
42
|
+
### Stream it from HTTP
|
|
43
|
+
|
|
44
|
+
```typescript title="src/flows/support/assist.ts"
|
|
45
|
+
import { on, flow, http } from "okengine";
|
|
46
|
+
import { z } from "zod";
|
|
47
|
+
import { support } from "@/core/ai";
|
|
48
|
+
|
|
49
|
+
export const assist = on(
|
|
50
|
+
http.post({ in: z.object({ message: z.string().min(1) }) }).public(),
|
|
51
|
+
flow({
|
|
52
|
+
do: ({ message }, fx) => fx.json.stream(fx.run(support, { message }, { stream: true })),
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The response is `text/event-stream`. Pass your own `threadId` when the chat already has one. The default is a unique id. It is not a per-process counter.
|
|
58
|
+
|
|
59
|
+
</Step>
|
|
60
|
+
|
|
61
|
+
<Step>
|
|
62
|
+
### Read the frames
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
curl -N -X POST http://localhost:6530/support/assist \
|
|
66
|
+
-H "content-type: application/json" \
|
|
67
|
+
-d '{"message":"Where is order 14?"}'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Each `data:` line is one JSON event. A text reply with no tool call looks like this (ids vary):
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
data: {"type":"RUN_STARTED","threadId":"agent-run-1","runId":"agent-run-1"}
|
|
74
|
+
|
|
75
|
+
data: {"type":"STEP_STARTED","stepName":"step-1"}
|
|
76
|
+
|
|
77
|
+
data: {"type":"TEXT_MESSAGE_START","messageId":"m-1","role":"assistant"}
|
|
78
|
+
|
|
79
|
+
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"m-1","delta":"Order 14 is open."}
|
|
80
|
+
|
|
81
|
+
data: {"type":"TEXT_MESSAGE_END","messageId":"m-1"}
|
|
82
|
+
|
|
83
|
+
data: {"type":"STEP_FINISHED","stepName":"step-1"}
|
|
84
|
+
|
|
85
|
+
data: {"type":"RUN_FINISHED","threadId":"agent-run-1","runId":"agent-run-1","result":{"cost":0.01,"stopReason":"completed","output":"Order 14 is open."},"usage":[{"inputTokens":3,"outputTokens":2}]}
|
|
86
|
+
|
|
87
|
+
data: [DONE]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`usage` is present only when the model reported token counts. `cost` and `stopReason` are on `result`, not on `usage`.
|
|
91
|
+
|
|
92
|
+
</Step>
|
|
93
|
+
|
|
94
|
+
<Step>
|
|
95
|
+
### Parse with the client
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { readAgentEvents } from "okengine/client/agent";
|
|
99
|
+
|
|
100
|
+
const response = await fetch("http://localhost:6530/support/assist", {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "content-type": "application/json" },
|
|
103
|
+
body: JSON.stringify({ message: "Where is order 14?" }),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
for await (const event of readAgentEvents(response)) {
|
|
107
|
+
if (event.type === "TEXT_MESSAGE_CONTENT") process.stdout.write(event.delta);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Import `okengine/client/agent`. That module stays off `okengine/client`.
|
|
112
|
+
|
|
113
|
+
</Step>
|
|
114
|
+
|
|
115
|
+
</Steps>
|
|
116
|
+
|
|
117
|
+
## Event reference
|
|
118
|
+
|
|
119
|
+
Core names match AG-UI. Subagent notices are `CUSTOM`, not extra types.
|
|
120
|
+
|
|
121
|
+
| Type | Fields | When it fires |
|
|
122
|
+
| ---------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
123
|
+
| `RUN_STARTED` | `threadId`, `runId` | The run begins. `threadId` is the option you passed, or the run id. |
|
|
124
|
+
| `STEP_STARTED` | `stepName` | A step starts. `maxSteps` counts tool calls, not model calls. Names are `step-1`, `step-2`, … |
|
|
125
|
+
| `TEXT_MESSAGE_START` | `messageId`, `role` | Assistant text is about to arrive. `role` is `assistant`. Empty text is skipped. |
|
|
126
|
+
| `TEXT_MESSAGE_CONTENT` | `messageId`, `delta` | One piece of assistant text. A streaming driver emits one frame per delta. A driver without `stream` emits the whole turn in one frame. |
|
|
127
|
+
| `TEXT_MESSAGE_END` | `messageId` | That text message is complete. |
|
|
128
|
+
| `TOOL_CALL_START` | `toolCallId`, `toolCallName`, `parentMessageId?` | The model named a tool. `parentMessageId` is set when that step emitted assistant text. |
|
|
129
|
+
| `TOOL_CALL_ARGS` | `toolCallId`, `delta` | One piece of the tool arguments. Streaming drivers emit each args delta. A driver without `stream` emits one JSON string. |
|
|
130
|
+
| `TOOL_CALL_END` | `toolCallId` | Arguments are complete. The tool has not run yet. |
|
|
131
|
+
| `TOOL_CALL_RESULT` | `messageId`, `toolCallId`, `content`, `role` | The tool returned. `role` is `tool`. `content` is a string. |
|
|
132
|
+
| `STEP_FINISHED` | `stepName` | That model step is done, including its tool calls. |
|
|
133
|
+
| `RUN_FINISHED` | `threadId`, `runId`, `result?`, `usage?`, `outcome?` | The loop stopped, or a tool is waiting for approval. |
|
|
134
|
+
| `RUN_ERROR` | `message`, `code?` | The run failed and did not finish. `code` is the error name when it is not `Error`. |
|
|
135
|
+
| `CUSTOM` | `name`, `value` | A nested agent started, finished, or failed. |
|
|
136
|
+
|
|
137
|
+
`RUN_FINISHED.result` is `{ cost, stopReason, output }`. `cost` is the sum of model-reported spend for this run. `output` is the last tool result. A text reply with no tool call may be the raw provider payload, not the assistant string.
|
|
138
|
+
|
|
139
|
+
`RUN_FINISHED.usage` is a one-element array, `{ inputTokens?, outputTokens? }`, and only when those numbers came from the model. Counts are never invented.
|
|
140
|
+
|
|
141
|
+
`messageId` values are `m-1`, `m-2`, … inside the run. A missing provider tool-call id becomes `agent:step:index`.
|
|
142
|
+
|
|
143
|
+
## Tool approval
|
|
144
|
+
|
|
145
|
+
A tool with `approval` and `gate` parks a durable Flow. The stream emits `RUN_FINISHED` with `outcome.type` `"interrupt"`, then `data: [DONE]`, then the response ends. The browser does not stay open. The approval id is base64url of `runId.toolCallId`, so it carries the durable Flow run id.
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"type": "RUN_FINISHED",
|
|
150
|
+
"threadId": "agent-run-1",
|
|
151
|
+
"runId": "agent-run-1",
|
|
152
|
+
"outcome": {
|
|
153
|
+
"type": "interrupt",
|
|
154
|
+
"interrupts": [
|
|
155
|
+
{
|
|
156
|
+
"id": "VjFTdEdYUjhfWjVqZEhpNkItbXlULmNhbGxfMQ",
|
|
157
|
+
"reason": "approval",
|
|
158
|
+
"payload": { "tool": "refund", "args": { "amount": 10 } }
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`id` is base64url of `<durable Flow run id>.<tool call id>`. The example decodes to `V1StGXR8_Z5jdHi6B-myT.call_1`. `V1StGXR8_Z5jdHi6B-myT` is the journal run id, not `agent-run-1`. `runId` on the frame is the agent stream id.
|
|
166
|
+
|
|
167
|
+
Show `payload.tool` and `payload.args`. Keep `id`. That id is the only handle for the decision.
|
|
168
|
+
|
|
169
|
+
Resolve it from a Flow, or from the built-in routes. The routes are public so the request can arrive. The tool's gate is checked on the call. A denying gate is 403. A missing id, or a tenant that does not match the park, is 404.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
await fx.agent.approve(id, { args: { amount: 4 } });
|
|
173
|
+
await fx.agent.deny(id, { reason: "over the limit" });
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
curl -X POST http://localhost:6530/agent/approvals/approve \
|
|
178
|
+
-H "content-type: application/json" \
|
|
179
|
+
-d '{"id":"VjFTdEdYUjhfWjVqZEhpNkItbXlULmNhbGxfMQ","args":{"amount":4}}'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
curl -X POST http://localhost:6530/agent/approvals/deny \
|
|
184
|
+
-H "content-type: application/json" \
|
|
185
|
+
-d '{"id":"VjFTdEdYUjhfWjVqZEhpNkItbXlULmNhbGxfMQ","reason":"over the limit"}'
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`args` replaces the tool input. `reason` is what the model sees after a deny. The first decision wins. The default wait is `24h`, then the tool is denied with reason `timeout`.
|
|
189
|
+
|
|
190
|
+
| Outcome | HTTP | What you do |
|
|
191
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------- |
|
|
192
|
+
| `{ ok: true }` | 200 `{ "data": { "ok": true }, "error": null }` | The journal sleep wakes now. |
|
|
193
|
+
| Already resolved | 409 `Conflict`. No `Retry-After`. Message: `That value is already in use.` | Stop. Do not retry. |
|
|
194
|
+
| Lease held | 409 `JournalLeaseBusy` with `Retry-After`. Message: `This run is locked by another worker. Retry after the given delay.` | Retry after the delay. |
|
|
195
|
+
|
|
196
|
+
Approving or denying does not continue this SSE response. Follow the run to see the rest.
|
|
197
|
+
|
|
198
|
+
## Follow a run
|
|
199
|
+
|
|
200
|
+
`GET /agent/runs/:runId/events` is `text/event-stream`. Each frame has an `id`. Send that id as `Last-Event-ID` to resume without gaps or duplicates. The route checks the same gate and tenant as the Flow that started the run. Another tenant, or a gate that denies the caller, is rejected.
|
|
201
|
+
|
|
202
|
+
An approval interrupt does not end this follow. The stream stays open through the interrupt and continues to the terminal `RUN_FINISHED`. The first run stream still closes after the interrupt. Disconnect, then resume with the last id you received.
|
|
203
|
+
|
|
204
|
+
The run stream yields every token. The follow log coalesces text and argument deltas (about every 100 ms or 1 KB, including a timer) and stores structural events one by one. The instance that holds the journal lease is the only writer. It reads the highest seq before it appends, and a repeated seq is an error. Followers read `seq` greater than the last id, not the whole run.
|
|
205
|
+
|
|
206
|
+
The log keeps 5,000 rows per run. Past that, deltas stop and one `CUSTOM` event named `oke.events.truncated` is stored. Structural events, approval interrupts, `RUN_FINISHED`, and `RUN_ERROR` are still stored. Earlier rows stay, so a follower can resume and seq numbers do not skip. A finished run's events are deleted after 24 hours. An unfinished run older than 7 days is closed with `RUN_ERROR` and deleted. The scheduler reads the store, not the process that opened the run. One instance claims the journal lease before it closes that run. The log is stored on the journal driver and keyed by the agent run id. `flow.retry` keeps that id. A follower must be the starting principal or an operator, and must pass every gate on the Flow.
|
|
207
|
+
|
|
208
|
+
The file journal is single-process. It is not for large logs. Use the Postgres journal driver in production.
|
|
209
|
+
|
|
210
|
+
A stored row on the live run stream carries `id:` set to that row's seq. Resume with `Last-Event-ID` so text you already showed is not sent again.
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { approve, deny, readAgentEvents } from "okengine/client/agent";
|
|
214
|
+
|
|
215
|
+
for await (const event of readAgentEvents(`http://localhost:6530/agent/runs/${runId}/events`)) {
|
|
216
|
+
if (event.type === "RUN_FINISHED" && event.outcome?.type === "interrupt") {
|
|
217
|
+
await approve("http://localhost:6530/agent/approvals/approve", event.outcome.interrupts[0]!.id);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`approve` and `deny` retry `JournalLeaseBusy` and throw `Conflict` when the decision is already stored. `useAgentRun` from `okengine/client-react` sends the message, keeps the text so far, and follows once after an interrupt. Approve and deny do not open a second follow.
|
|
223
|
+
|
|
224
|
+
## Patterns
|
|
225
|
+
|
|
226
|
+
<Tabs items={["One message", "History", "Thread id", "Subagents"]}>
|
|
227
|
+
|
|
228
|
+
<Tab value="One message">
|
|
229
|
+
|
|
230
|
+
A string and `{ message }` are the same turn. Passing both throws `fx.run: pass message or messages, not both`.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
return fx.json.stream(fx.run(support, "Where is order 14?", { stream: true }));
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
</Tab>
|
|
237
|
+
|
|
238
|
+
<Tab value="History">
|
|
239
|
+
|
|
240
|
+
`messages` is the prior thread. Roles are `system`, `user`, `assistant`, and `tool`.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
return fx.json.stream(
|
|
244
|
+
fx.run(
|
|
245
|
+
support,
|
|
246
|
+
{
|
|
247
|
+
messages: [
|
|
248
|
+
{ role: "user", content: "Where is order 14?" },
|
|
249
|
+
{ role: "assistant", content: "Looking it up." },
|
|
250
|
+
{ role: "user", content: "Refund it." },
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
{ stream: true },
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
</Tab>
|
|
259
|
+
|
|
260
|
+
<Tab value="Thread id">
|
|
261
|
+
|
|
262
|
+
Pass `threadId` when the chat already has one. The default is a unique id.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
return fx.json.stream(fx.run(support, { message }, { stream: true, threadId: chatId }));
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`RUN_STARTED` and `RUN_FINISHED` both carry that `threadId`.
|
|
269
|
+
|
|
270
|
+
`mock`, `anthropic`, and `openai-compatible` stream tokens and tool-call arguments. `bedrock` and `vertex` are reserved driver ids: boot throws before a call. `fx.stream` uses the same `stream` method, so it works on Anthropic too.
|
|
271
|
+
|
|
272
|
+
</Tab>
|
|
273
|
+
|
|
274
|
+
<Tab value="Subagents">
|
|
275
|
+
|
|
276
|
+
A tool that names another agent emits `CUSTOM` on the parent stream. `okengine/client/agent` also returns typed names.
|
|
277
|
+
|
|
278
|
+
| `CUSTOM.name` | Parsed `type` | `value` |
|
|
279
|
+
| ----------------------- | ------------------- | ---------------------------- |
|
|
280
|
+
| `oke.subagent.started` | `subagent.started` | `runId`, `parentToolCallId?` |
|
|
281
|
+
| `oke.subagent.finished` | `subagent.finished` | `runId`, `parentToolCallId?` |
|
|
282
|
+
| `oke.subagent.error` | `subagent.error` | `runId`, `parentToolCallId?` |
|
|
283
|
+
|
|
284
|
+
Any other `CUSTOM` name stays `CUSTOM`. Nesting stops at `maxDepth` (default 3).
|
|
285
|
+
|
|
286
|
+
</Tab>
|
|
287
|
+
|
|
288
|
+
</Tabs>
|
|
289
|
+
|
|
290
|
+
## Text stream or event stream
|
|
291
|
+
|
|
292
|
+
| Call | What each SSE `data:` frame is |
|
|
293
|
+
| -------------------------------------------------------- | ---------------------------------------------- |
|
|
294
|
+
| `fx.json.stream(fx.stream(model, { prompt }))` | A JSON string of plain text (`"Hel"`, `"lo"`). |
|
|
295
|
+
| `fx.json.stream(fx.run(agent, input, { stream: true }))` | One AG-UI event object. |
|
|
296
|
+
|
|
297
|
+
`fx.stream` is the model text iterator. `fx.json.stream` is the SSE carrier. Agent UIs use the second row.
|
|
298
|
+
|
|
299
|
+
## Abort and stop reasons
|
|
300
|
+
|
|
301
|
+
Disconnecting the HTTP client aborts the in-flight model call. The run records `stopReason` `aborted`. If the frame is still written, it is `RUN_ERROR` with `code` `AbortError`. `message` may be the provider's abort text, not only `aborted`.
|
|
302
|
+
|
|
303
|
+
`result.stopReason` on a normal `RUN_FINISHED`:
|
|
304
|
+
|
|
305
|
+
| `stopReason` | Meaning |
|
|
306
|
+
| ------------ | ------------------------------------------------------------------------------------------ |
|
|
307
|
+
| `completed` | The model stopped calling tools. A gate denial fed back to the model stays `completed`. |
|
|
308
|
+
| `max_steps` | The loop hit `maxSteps` (default 6). |
|
|
309
|
+
| `budget` | Spend reached `budget.maxCostPerRun`. The trail so far is the output. This does not throw. |
|
|
310
|
+
|
|
311
|
+
`denied` (unknown tool) and `aborted` arrive as `RUN_ERROR`, not as `result.stopReason`. A thrown tool ends with `RUN_FINISHED` and `result.stopReason` `"error"`, then `data: [DONE]`. The same close follows an approval interrupt: the final frame, then `[DONE]`, then the body ends.
|
|
312
|
+
|
|
313
|
+
## Troubleshooting
|
|
314
|
+
|
|
315
|
+
<Accordions>
|
|
316
|
+
|
|
317
|
+
<Accordion title='ai: flow "support.assist" must set durable: true to run agent "support"'>
|
|
318
|
+
The tool has `approval`, and the Flow is not durable. The message names that Flow: `ai: flow
|
|
319
|
+
"support.assist" must set durable: true to run agent "support"`. Set `durable: true`. A false
|
|
320
|
+
`approval` predicate does not park.
|
|
321
|
+
</Accordion>
|
|
322
|
+
|
|
323
|
+
<Accordion title='RUN_ERROR: ai: model requested unknown tool "refund"'>
|
|
324
|
+
The model called a tool that is not on `ai.agent({tools})`. `code` is `AgentLoopHalt`. Add the
|
|
325
|
+
Flow name to `tools`, or stop offering it in the prompt.
|
|
326
|
+
</Accordion>
|
|
327
|
+
|
|
328
|
+
<Accordion title="RUN_FINISHED stopReason is budget">
|
|
329
|
+
`result.stopReason` is `budget` and `result.cost` is the spend so far. Raise
|
|
330
|
+
`budget.maxCostPerRun`, or treat the partial `output` as the reply. A prompt's `maxCostPerCall` is
|
|
331
|
+
different: `fx.ask` throws `ai: prompt "…" exceeded maxCostPerCall …` (`AiBudgetExceededError`).
|
|
332
|
+
</Accordion>
|
|
333
|
+
|
|
334
|
+
<Accordion title="The client disconnected">
|
|
335
|
+
The model call aborts. You may see `RUN_ERROR` with message `aborted` and code `AbortError`, or
|
|
336
|
+
only a closed socket. The recorded `stopReason` is `aborted`.
|
|
337
|
+
</Accordion>
|
|
338
|
+
|
|
339
|
+
<Accordion title="A frame is not one of the core types">
|
|
340
|
+
`readAgentEvents` yields `CUSTOM` unchanged when `name` is not `oke.subagent.started`,
|
|
341
|
+
`oke.subagent.finished`, or `oke.subagent.error`. Those three become `subagent.started`,
|
|
342
|
+
`subagent.finished`, and `subagent.error`. An object with no `type` is skipped. `data: [DONE]` is
|
|
343
|
+
skipped. The iterator ends when the body ends, not because of that line.
|
|
344
|
+
</Accordion>
|
|
345
|
+
|
|
346
|
+
<Accordion title="Conflict or JournalLeaseBusy">
|
|
347
|
+
A second approve or deny after the decision is stored is `Conflict`. Do not retry it. A lost race
|
|
348
|
+
while another worker holds the run is `JournalLeaseBusy` with `Retry-After`. Retry only that code.
|
|
349
|
+
</Accordion>
|
|
350
|
+
|
|
351
|
+
</Accordions>
|
|
352
|
+
|
|
353
|
+
## Learn more
|
|
354
|
+
|
|
355
|
+
- [Agents](/docs/elements/ai/agents) — `fx.run`, tools, `maxSteps`, and approval
|
|
356
|
+
- [Prompts](/docs/elements/ai/prompts) — tool-less `fx.ask` still throws on `maxCostPerCall`
|
|
357
|
+
- [MCP](/docs/elements/ai/mcp) — external tools in the same loop
|
|
358
|
+
|
|
359
|
+
## Next
|
|
360
|
+
|
|
361
|
+
<Cards>
|
|
362
|
+
<Card
|
|
363
|
+
title="Agents"
|
|
364
|
+
description="Bounded tool loops with fx.run."
|
|
365
|
+
href="/docs/elements/ai/agents"
|
|
366
|
+
/>
|
|
367
|
+
<Card
|
|
368
|
+
title="Decisions"
|
|
369
|
+
description="One calibrated choice, with a person when it cannot auto."
|
|
370
|
+
href="/docs/elements/ai/decide"
|
|
371
|
+
/>
|
|
372
|
+
<Card title="AI" description="Models, prompts, agents, and decisions." href="/docs/elements/ai" />
|
|
373
|
+
</Cards>
|
|
@@ -279,6 +279,11 @@ Compose does not pin inference — BYO URL + keys, or [OpenRouter](/docs/recipes
|
|
|
279
279
|
|
|
280
280
|
<Accordions>
|
|
281
281
|
|
|
282
|
+
<Accordion title="The same input called the model again">
|
|
283
|
+
Outside a durable Flow, `fx.ask` always reaches the model. Replay lives only on that run's
|
|
284
|
+
journal.
|
|
285
|
+
</Accordion>
|
|
286
|
+
|
|
282
287
|
<Accordion title='ai: unknown prompt "…"'>
|
|
283
288
|
`fx.ask` named a prompt that was never minted with `model.prompt`, or the declaring module was not
|
|
284
289
|
imported before `oke()`.
|
|
@@ -295,8 +300,8 @@ Compose does not pin inference — BYO URL + keys, or [OpenRouter](/docs/recipes
|
|
|
295
300
|
</Accordion>
|
|
296
301
|
|
|
297
302
|
<Accordion title="AiBudgetExceededError">
|
|
298
|
-
Cause: `ai: prompt "…" exceeded maxCostPerCall N
|
|
299
|
-
|
|
303
|
+
Cause: `ai: prompt "…" exceeded maxCostPerCall N`. A tool-less `fx.ask` throws
|
|
304
|
+
`AiBudgetExceededError`. `fx.run` returns the partial result when the agent budget is spent.
|
|
300
305
|
</Accordion>
|
|
301
306
|
|
|
302
307
|
<Accordion title="build failed: … without allowPii">
|
|
@@ -316,9 +321,10 @@ Compose does not pin inference — BYO URL + keys, or [OpenRouter](/docs/recipes
|
|
|
316
321
|
- [Models](/docs/elements/ai/models) — provider registry and `baseUrl` rules
|
|
317
322
|
- [Prompts](/docs/elements/ai/prompts) — `via`, budgets, versions, evals
|
|
318
323
|
- [Agents](/docs/elements/ai/agents) — `fx.run`, tools, `maxSteps`
|
|
324
|
+
- [Decisions](/docs/elements/ai/decide) — `fx.decide`, `how`, and the lockfile
|
|
319
325
|
- [MCP](/docs/elements/ai/mcp) — inbound `mcp.tool` and outbound `ai.mcpServer`
|
|
320
326
|
- [OpenRouter](/docs/recipes/openrouter) — zero-Docker cloud path
|
|
321
|
-
- [fx](/docs/reference/fx) — `fx.ask` / `fx.run` / `fx.embed`
|
|
327
|
+
- [fx](/docs/reference/fx) — `fx.ask` / `fx.run` / `fx.decide` / `fx.embed`
|
|
322
328
|
- [Errors](/docs/reference/errors) — OKE1005 · OKE1009
|
|
323
329
|
|
|
324
330
|
## Next
|
|
@@ -128,8 +128,9 @@ smart.prompt("ticket-triage", {
|
|
|
128
128
|
});
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
`budget.maxCostPerRun`
|
|
131
|
+
A tool-less ask still throws `AiBudgetExceededError` at `maxCostPerCall`. An agent run
|
|
132
|
+
returns the partial result when `budget.maxCostPerRun` is spent. `repair: 1` sends one
|
|
133
|
+
follow-up of the original messages plus the mismatch; it counts toward the cap and is its own journal entry.
|
|
133
134
|
|
|
134
135
|
</Tab>
|
|
135
136
|
|
|
@@ -161,6 +162,7 @@ Default `maxSteps` is `6`. Tool names also stamp `effects.calls`.
|
|
|
161
162
|
| `version` | `number` | — | Pin with `fx.ask("name@N")` |
|
|
162
163
|
| `evals` | `string` | — | Path for `oke eval` JSONL |
|
|
163
164
|
| `budget` | `{ maxCostPerCall?, maxCostPerRun? }` | — | Cost contracts |
|
|
165
|
+
| `repair` | `0` \| `1` | `0` | One schema-mismatch follow-up; counts toward `maxCostPerCall` |
|
|
164
166
|
| `via` | `string[]` | `[parent model]` | Recovery chain of logical names |
|
|
165
167
|
| `timeout` | `"30s"` \| ms | — | Ask deadline (not a cost budget) |
|
|
166
168
|
| `in` | Schema | — | Declared / Manifest / doctor — **not** runtime-validated on ask |
|
|
@@ -58,7 +58,7 @@ Response:
|
|
|
58
58
|
<Callout title="Omit path and name">
|
|
59
59
|
Tree default: `http.get()` and `flow({ do })` — no path or name strings. The
|
|
60
60
|
file stamps both. Pass either only for
|
|
61
|
-
[control](/docs/
|
|
61
|
+
[control](/docs/understand/routing#when-to-omit--when-to-pass).
|
|
62
62
|
</Callout>
|
|
63
63
|
|
|
64
64
|
## Progressive Patterns
|
|
@@ -180,7 +180,7 @@ export const deleteAccount = on(
|
|
|
180
180
|
## Path Conventions
|
|
181
181
|
|
|
182
182
|
**Default — omit the path.** Tree files stamp the URL from disk (`http.get()`).
|
|
183
|
-
Pass a path only for [control](/docs/
|
|
183
|
+
Pass a path only for [control](/docs/understand/routing#when-to-omit--when-to-pass).
|
|
184
184
|
|
|
185
185
|
**Control — explicit path** — pass the URL template when the folder should not own the route:
|
|
186
186
|
|
|
@@ -197,7 +197,7 @@ import { on, flow, http } from "okengine";
|
|
|
197
197
|
export const get = on(http.get(), flow({ do: async ({ id }) => ({ id }) }));
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
-
Full file-tree rules: [Routing](/docs/
|
|
200
|
+
Full file-tree rules: [Routing](/docs/understand/routing).
|
|
201
201
|
|
|
202
202
|
## Request Parsing
|
|
203
203
|
|
|
@@ -268,7 +268,7 @@ export const create = on(
|
|
|
268
268
|
## HTTP Methods
|
|
269
269
|
|
|
270
270
|
Each verb binds with `on(http.<method>(), flow({…}))`. Omit the path on tree
|
|
271
|
-
files; pass one only for [control](/docs/
|
|
271
|
+
files; pass one only for [control](/docs/understand/routing#when-to-omit--when-to-pass).
|
|
272
272
|
|
|
273
273
|
<Tabs items={["GET", "POST", "PUT", "PATCH", "DELETE", "QUERY", "HEAD", "OPTIONS"]}>
|
|
274
274
|
|
|
@@ -61,7 +61,7 @@ Response:
|
|
|
61
61
|
<Callout title="Omit path and name">
|
|
62
62
|
Tree default: `http.get()` + `flow({ do })` — no path or name strings. Pass
|
|
63
63
|
either only for
|
|
64
|
-
[control](/docs/
|
|
64
|
+
[control](/docs/understand/routing#when-to-omit--when-to-pass).
|
|
65
65
|
</Callout>
|
|
66
66
|
|
|
67
67
|
<Callout title="Call-only Flows">
|
|
@@ -200,7 +200,7 @@ const { chargeId } = await fx.call(chargeCard, { amount: 50 });
|
|
|
200
200
|
<Card
|
|
201
201
|
title="Routing"
|
|
202
202
|
description="File-tree stamps for HTTP paths, Flow names, and client units."
|
|
203
|
-
href="/docs/
|
|
203
|
+
href="/docs/understand/routing"
|
|
204
204
|
/>
|
|
205
205
|
<Card
|
|
206
206
|
title="Consumers"
|
|
@@ -592,7 +592,7 @@ Default is `true` once `gate.auth.tenant` is on.
|
|
|
592
592
|
|
|
593
593
|
- [The Architecture](/docs/understand/the-architecture) — `on`, trigger, `flow`, `do`, `fx`
|
|
594
594
|
- [HTTP](/docs/elements/flow/http) — verbs, envelopes, resources, live SSE
|
|
595
|
-
- [Routing](/docs/
|
|
595
|
+
- [Routing](/docs/understand/routing) — file-tree stamps, barrels, OKE1030 · OKE1040–1045
|
|
596
596
|
- [Consumers](/docs/elements/flow/consumers) — Signal / Clock / CDC
|
|
597
597
|
- [Workflows](/docs/elements/flow/workflows) — `durable: true` + `fx.step`
|
|
598
598
|
- [fx](/docs/reference/fx) — every method inside `do`
|
|
@@ -103,8 +103,11 @@ export const app = oke({
|
|
|
103
103
|
});
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
In production, set `gate.auth.secret` (or `OKE_AUTH_SECRET`). Omitting
|
|
106
|
+
In production, set `gate.auth.secret` (or `OKE_AUTH_SECRET`). Omitting both in `prod` throws:
|
|
107
107
|
`gate.auth: secret is required in production (set gate.auth.secret or OKE_AUTH_SECRET)`.
|
|
108
|
+
When `gate.auth.secret` is omitted, the process uses `OKE_AUTH_SECRET`. `oke dev` writes one
|
|
109
|
+
value into `.env.local` and passes it to Console and the app, so a Bearer API key created
|
|
110
|
+
in Console verifies on the app.
|
|
108
111
|
|
|
109
112
|
Forged, expired, or revoked access tokens map to typed `Unauthorized` — they never become a
|
|
110
113
|
principal.
|
|
@@ -544,11 +544,11 @@ writes (`set` · `delete`). Declare the same refs when you hand-write
|
|
|
544
544
|
|
|
545
545
|
## Drivers
|
|
546
546
|
|
|
547
|
-
| Driver | Runs as
|
|
548
|
-
| -------------------------------------------- |
|
|
549
|
-
| `redis` |
|
|
550
|
-
| `memory` | Process map
|
|
551
|
-
| Durable (`postgres` / `pglite` via `oke_kv`) | Shared SQL URL
|
|
547
|
+
| Driver | Runs as | Best for |
|
|
548
|
+
| -------------------------------------------- | --------------------------------- | ---------------------------------- |
|
|
549
|
+
| `redis` | Dragonfly by default (Redis-wire) | Dev + prod default |
|
|
550
|
+
| `memory` | Process map | Test default |
|
|
551
|
+
| Durable (`postgres` / `pglite` via `oke_kv`) | Shared SQL URL | Namespaces that must outlive Redis |
|
|
552
552
|
|
|
553
553
|
Defaults: `redis` / `memory` / `redis` (dev / test / prod). Pin overrides in
|
|
554
554
|
`oke.config.ts`; image pins stay under `images.store.kv` — the driver id stays
|
|
@@ -209,10 +209,25 @@ Builtin encrypted store:
|
|
|
209
209
|
|
|
210
210
|
`--url` overrides the SQL URL (`DATABASE_URL` / `OKE_STORE_SQL_URL`).
|
|
211
211
|
|
|
212
|
+
## Audit sinks
|
|
213
|
+
|
|
214
|
+
| `vault.audit.sink` | Recorded as | `oke vault audit` |
|
|
215
|
+
| ------------------ | --------------------------------- | ------------------- |
|
|
216
|
+
| `db` (default) | SQL hash chain | list, verify, purge |
|
|
217
|
+
| `stdout` | One secret-free JSON line | Refused |
|
|
218
|
+
| `webhook` | POST of that JSON to `webhookUrl` | Refused |
|
|
219
|
+
|
|
220
|
+
`webhookUrl` must be an absolute `http:` or `https:` URL. The POST uses `redirect: "error"` and a 5 second timeout. A failed delivery does not undo the vault write. `audit.enabled: false` records nothing. The body never contains a secret value.
|
|
221
|
+
|
|
212
222
|
## Troubleshooting
|
|
213
223
|
|
|
214
224
|
<Accordions>
|
|
215
225
|
|
|
226
|
+
<Accordion title="audit verify requires audit.sink db">
|
|
227
|
+
`stdout` and `webhook` sinks are not a local hash chain. `oke vault audit` (list, verify, purge)
|
|
228
|
+
throws `UNSUPPORTED`. Use `sink: "db"` when you need `verify`.
|
|
229
|
+
</Accordion>
|
|
230
|
+
|
|
216
231
|
<Accordion title="oke vault: no such secret">
|
|
217
232
|
Rotate/get targeted a path that was never set. `oke vault list` (or Console) for live paths;
|
|
218
233
|
remember per-tenant storage uses `{tenantId}/{name}`.
|