okengine 0.22.0 → 0.23.1
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 +7 -0
- package/manifest.v1.schema.json +44 -0
- package/package.json +32 -19
- package/site/content/docs/ai/mcp.mdx +14 -10
- package/site/content/docs/ai/skills.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/index.mdx +4 -4
- package/site/content/docs/elements/channel/receipts.mdx +7 -5
- package/site/content/docs/elements/channel/sms.mdx +2 -1
- package/site/content/docs/elements/flow/index.mdx +22 -18
- package/site/content/docs/elements/gate/auth.mdx +4 -1
- package/site/content/docs/elements/signal/index.mdx +15 -14
- package/site/content/docs/elements/store/index.mdx +1 -1
- package/site/content/docs/elements/store/kv.mdx +5 -5
- package/site/content/docs/plugins/otp.mdx +3 -3
- package/site/content/docs/recipes/dragonfly.mdx +5 -5
- package/site/content/docs/recipes/index.mdx +6 -6
- 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 +9 -7
- package/site/content/docs/reference/fx.mdx +19 -10
- 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/auth/tenants.ts +17 -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/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/doctor-diff.test.ts +29 -1
- package/src/cli/doctor-diff.ts +35 -0
- package/src/cli/eval.ts +142 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/load-config.images.test.ts +2 -2
- package/src/cli/load-config.ts +1 -1
- package/src/cli/manifest-pr-diff.ts +16 -0
- package/src/cli/mcp-from-console.ts +48 -0
- package/src/cli/registry.ts +35 -1
- package/src/client/agent.ts +307 -0
- package/src/client/transport.test.ts +20 -0
- package/src/client/transport.ts +2 -1
- package/src/client/types.ts +1 -1
- 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-fetch.test.ts +77 -0
- package/src/compiler/effects-infer.ts +114 -17
- package/src/compiler/effects-join.test.ts +48 -0
- package/src/compiler/extract.test.ts +28 -0
- package/src/compiler/extract.ts +279 -13
- package/src/compiler/fx-index.ts +58 -4
- package/src/compiler/response.ts +35 -2
- package/src/config/index.ts +21 -3
- package/src/console/index.ts +1 -1
- 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.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-C_N4qvai.js +4 -0
- package/src/console/ui-next/dist/assets/{agent-disclosure-Bkohc_8X.js → agent-disclosure-Cq1TxniW.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-rL0lHxat.js → cache-glyph-BaI4uxZz.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-Deo4PP18.js → call-pii-button-DNyK7IIY.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-CoJ6amHf.js → collapsible-CRhBJMZR.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-D9twj8Xy.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-CsHWlVPU.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-ZaTJAhR9.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CPYBzDh0.js → highlighted-json-DQ-EMAfz.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-DTPevKmM.js → http-method-Cn_Fl79s.js} +1 -1
- package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
- package/src/console/ui-next/dist/assets/index-CmRFeWdT.js +58 -0
- package/src/console/ui-next/dist/assets/observability-page-Cn6R_dXz.js +8 -0
- package/src/console/ui-next/dist/assets/{replica-lag-BM27lXld.js → replica-lag-Bx6EZhSe.js} +7 -7
- package/src/console/ui-next/dist/assets/request-meta-DmdiUrfh.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-8P6vGXnn.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-BfGvpFSv.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CWdUHbc0.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-CZOFZzay.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-ki_ishmX.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 +6 -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/recipes/dragonfly.ts +3 -2
- package/src/docker/recipes/redis.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.test.ts +22 -1
- package/src/drivers/journal-postgres.ts +511 -13
- 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/signal-compete.test.ts +234 -0
- package/src/drivers/signal-postgres.ts +39 -19
- package/src/drivers/signal-redis.ts +167 -11
- package/src/drivers/signal-types.ts +12 -0
- 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/mcp-protocol.ts +5 -6
- 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 +1187 -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/channel/runtime.ts +11 -0
- package/src/elements/channel/sql-ledger.ts +251 -0
- package/src/elements/clock/durable.ts +27 -1
- package/src/elements/signal/runtime.ts +9 -0
- package/src/elements/signal.test.ts +1 -0
- package/src/elements/store/cache.test.ts +120 -15
- package/src/elements/store/cache.ts +200 -39
- package/src/elements/store/prepare-row.test.ts +6 -2
- package/src/elements/store/sql-session.test.ts +53 -0
- package/src/elements/store/sql-session.ts +206 -10
- package/src/elements/store.ts +1 -0
- package/src/i18n/catalogs/ar.ts +5 -0
- package/src/i18n/catalogs/en.ts +5 -0
- package/src/index.ts +2 -0
- package/src/kernel/agent-event-store.ts +448 -0
- package/src/kernel/app.ts +135 -17
- package/src/kernel/auto-cache.test.ts +228 -0
- package/src/kernel/boot-bind/ai.ts +3 -0
- package/src/kernel/boot-bind/channel.ts +63 -2
- package/src/kernel/boot-bind/honor-config.test.ts +40 -10
- package/src/kernel/boot-bind/signal.ts +61 -25
- package/src/kernel/boot-bind/store.test.ts +26 -0
- package/src/kernel/boot-bind/store.ts +82 -6
- package/src/kernel/boot.ts +31 -6
- package/src/kernel/builtin-errors.ts +2 -0
- package/src/kernel/capability.ts +3 -0
- package/src/kernel/decision-label-store.ts +441 -0
- 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 +11 -0
- package/src/kernel/errors-text.ts +20 -0
- package/src/kernel/errors.ts +10 -0
- package/src/kernel/flow.test.ts +2 -3
- package/src/kernel/flow.ts +33 -3
- package/src/kernel/fx-call-types.test.ts +32 -0
- package/src/kernel/fx-decide.ts +765 -0
- package/src/kernel/fx-sql-handle.ts +46 -7
- package/src/kernel/fx.test.ts +13 -8
- package/src/kernel/fx.ts +339 -45
- package/src/kernel/http-frame.test.ts +94 -0
- package/src/kernel/http-frame.ts +203 -0
- package/src/kernel/idempotency.test.ts +2 -1
- package/src/kernel/journal.test.ts +114 -0
- package/src/kernel/journal.ts +471 -51
- package/src/kernel/json-result.ts +10 -0
- package/src/kernel/pipeline-tenant.ts +5 -1
- package/src/kernel/pipeline.test.ts +45 -0
- package/src/kernel/pipeline.ts +5 -0
- package/src/kernel/signal-tx.ts +28 -0
- package/src/kernel/sse-id.ts +32 -0
- package/src/kernel/store-transaction.test.ts +95 -0
- package/src/manifest/diff.ts +1 -0
- package/src/manifest/types.ts +29 -2
- package/src/mcp/ai-tools.test.ts +202 -0
- package/src/mcp/authorization.ts +66 -0
- package/src/mcp/docs-server.ts +19 -2
- package/src/mcp/mcp.test.ts +83 -0
- package/src/mcp/protocol.ts +8 -2
- package/src/mcp/server.ts +23 -2
- package/src/mcp/session.ts +3 -0
- package/src/mcp/tools.ts +168 -0
- package/src/mcp/versions.ts +60 -0
- package/src/plugins/index.ts +1 -0
- package/src/plugins/mena.ts +56 -0
- package/src/release/http-graph.test.ts +2 -0
- package/src/release/http-graph.ts +5 -0
- package/src/release/measure.ts +39 -14
- 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/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
- package/src/console/ui-next/dist/assets/access-page-Ze6ckjdd.js +0 -4
- package/src/console/ui-next/dist/assets/copy-inline-button-CWG5_ZOh.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-b_ZzIsmU.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-nwXEO1Ac.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-BbQ_8z50.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-CwyLpVXz.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-C8sSCqYT.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-D2E7BtKK.js +0 -1
- package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
- package/src/console/ui-next/dist/assets/index-vTuwmeQz.js +0 -58
- package/src/console/ui-next/dist/assets/observability-page-SJTSzHFJ.js +0 -4
- package/src/console/ui-next/dist/assets/request-meta-BjFT7DNl.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-CUNegEV4.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-Qj9ThRE-.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-Cmitq3da.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CbDIB-7x.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-_PuYqFty.js +0 -1
- package/src/console/ui-next/dist/assets/use-vault-list-Cs45Czkt.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-BOMr0go9.js +0 -2
package/src/kernel/fx.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type JsonStreamResult,
|
|
18
18
|
} from "./json-result.ts";
|
|
19
19
|
import type { Effects, ResourceRef, SignalResourceRef } from "../manifest/types.ts";
|
|
20
|
+
import type { FlowInput, FlowOutput } from "./flow.ts";
|
|
20
21
|
import { isMcpToolRef } from "../manifest/mcp-ref.ts";
|
|
21
22
|
import type { QueryPageSpec } from "./list-page.ts";
|
|
22
23
|
import type {
|
|
@@ -36,9 +37,13 @@ import { rlsIdentityFromAuth } from "../elements/store.ts";
|
|
|
36
37
|
import type { RlsIdentity } from "../drivers/pg-rls.ts";
|
|
37
38
|
import type { SignalDecl, SignalRuntime } from "../elements/signal.ts";
|
|
38
39
|
import type { DeadLetter, SignalEmitOptions } from "../drivers/signal-types.ts";
|
|
40
|
+
import { currentSignalTransaction } from "./signal-tx.ts";
|
|
39
41
|
import type { VaultActor, VaultAdapter, VaultRuntime } from "../elements/vault.ts";
|
|
40
42
|
import type { ChannelRuntime } from "../elements/channel.ts";
|
|
43
|
+
import type { AgentApprovalResolveResult } from "../elements/ai/approval.ts";
|
|
41
44
|
import type { AiRuntime } from "../elements/ai.ts";
|
|
45
|
+
import type { AgUiEvent } from "../elements/ai/events.ts";
|
|
46
|
+
import type { AiMessage } from "../drivers/ai-types.ts";
|
|
42
47
|
import { parseDurationMs } from "../elements/clock/duration.ts";
|
|
43
48
|
import type { ApiKeyStore } from "../auth/api-keys.ts";
|
|
44
49
|
import type { FxAuthIdentity, FxAuthKeyMethods } from "./fx-auth-keys.ts";
|
|
@@ -62,14 +67,14 @@ import {
|
|
|
62
67
|
touchDryRunStore,
|
|
63
68
|
} from "./dry-run.ts";
|
|
64
69
|
import type { FailFn } from "./errors.ts";
|
|
65
|
-
import { abortError, currentAbortSignal, linkAbort } from "./abort-scope.ts";
|
|
70
|
+
import { abortError, currentAbortSignal, linkAbort, withAbortSignal } from "./abort-scope.ts";
|
|
66
71
|
import type { FxRetryOptions, FxThunk } from "./concurrency.ts";
|
|
67
72
|
import { maskRedactedDeep, Redacted } from "./redacted.ts";
|
|
68
73
|
import type { JournalSession, JournalStepOptions } from "./journal.ts";
|
|
69
74
|
|
|
70
75
|
/** Options for {@link Fx.step} — see {@link JournalStepOptions}. */
|
|
71
76
|
export type StepOptions<T> = JournalStepOptions<T>;
|
|
72
|
-
import type
|
|
77
|
+
import { type RunTelemetry } from "./run-telemetry.ts";
|
|
73
78
|
import type { RunsRuntime } from "../runs/runtime.ts";
|
|
74
79
|
import type { RunsRow, WideEvent } from "../runs/types.ts";
|
|
75
80
|
import type { RunWindowStats, SloBreach } from "../runs/window.ts";
|
|
@@ -114,7 +119,33 @@ function loadFxLiveStream(): typeof import("./fx-live-stream.ts") {
|
|
|
114
119
|
return lazyRequire(import.meta.dir, ["fx", "live", "stream"].join("-"));
|
|
115
120
|
}
|
|
116
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Split `fx.run` input into one message or a history.
|
|
124
|
+
*
|
|
125
|
+
* @param input - String, `{ message }`, or `{ messages }`
|
|
126
|
+
*/
|
|
127
|
+
function agentTurn(input: unknown): { message?: string; messages?: readonly AiMessage[] } {
|
|
128
|
+
if (typeof input === "string") return { message: input };
|
|
129
|
+
if (input && typeof input === "object") {
|
|
130
|
+
const record = input as { message?: unknown; messages?: unknown };
|
|
131
|
+
const hasMessage = "message" in record && record.message !== undefined;
|
|
132
|
+
const hasMessages = "messages" in record && record.messages !== undefined;
|
|
133
|
+
if (hasMessage && hasMessages) {
|
|
134
|
+
throw new TypeError("fx.run: pass message or messages, not both");
|
|
135
|
+
}
|
|
136
|
+
if (hasMessages && Array.isArray(record.messages)) {
|
|
137
|
+
return { messages: record.messages as readonly AiMessage[] };
|
|
138
|
+
}
|
|
139
|
+
if (hasMessage) return { message: String(record.message) };
|
|
140
|
+
}
|
|
141
|
+
return { message: JSON.stringify(input ?? {}) };
|
|
142
|
+
}
|
|
143
|
+
|
|
117
144
|
/** `fx.fetch` — keep host parsing + dry-run stub off cold edge / Store-only graphs. */
|
|
145
|
+
function loadFxDecide(): typeof import("./fx-decide.ts") {
|
|
146
|
+
return lazyRequire(import.meta.dir, ["fx", "decide"].join("-"));
|
|
147
|
+
}
|
|
148
|
+
|
|
118
149
|
function loadFxFetch(): typeof import("./fx-fetch.ts") {
|
|
119
150
|
return lazyRequire(import.meta.dir, ["fx", "fetch"].join("-"));
|
|
120
151
|
}
|
|
@@ -143,6 +174,12 @@ export type { FxRetryOptions, FxThunk } from "./concurrency.ts";
|
|
|
143
174
|
/** Named ref: plain string or `{ name }` element handle (optional pin). */
|
|
144
175
|
export type NamedRef = string | { readonly name: string; readonly version?: number };
|
|
145
176
|
|
|
177
|
+
/** Input of {@link Fx.call}: the flow's input, or `unknown` for a name. */
|
|
178
|
+
type FxCallInput<F> = FlowInput<F>;
|
|
179
|
+
|
|
180
|
+
/** Result of {@link Fx.call}: the flow's output, or `unknown` for a name. */
|
|
181
|
+
type FxCallResult<F> = Promise<FlowOutput<F>>;
|
|
182
|
+
|
|
146
183
|
/** Resolve a {@link NamedRef} to its string id (`name@version` when pinned). */
|
|
147
184
|
export function resolveName(ref: NamedRef): string {
|
|
148
185
|
if (typeof ref === "string") return ref;
|
|
@@ -394,6 +431,8 @@ export interface FxAskOptions {
|
|
|
394
431
|
readonly tools?: readonly NamedRef[];
|
|
395
432
|
/** Bound on tool invocations (default 6). */
|
|
396
433
|
readonly maxSteps?: number;
|
|
434
|
+
/** Yield AG-UI events instead of resolving to the validated object. */
|
|
435
|
+
readonly stream?: boolean;
|
|
397
436
|
}
|
|
398
437
|
|
|
399
438
|
/** Options for {@link Fx.search}. */
|
|
@@ -406,11 +445,13 @@ export type { JsonResult, JsonStreamResult };
|
|
|
406
445
|
|
|
407
446
|
const sseFrameBrand: unique symbol = Symbol.for("oke.sse.frame");
|
|
408
447
|
|
|
409
|
-
/** One SSE frame — optional `id:` plus JSON `data
|
|
448
|
+
/** One SSE frame — optional `id:` plus JSON `data:`, or a comment line. */
|
|
410
449
|
export interface SseFrame {
|
|
411
450
|
readonly [sseFrameBrand]: true;
|
|
412
451
|
readonly data: unknown;
|
|
413
452
|
readonly id?: string;
|
|
453
|
+
/** Comment body. When set and `data` is undefined, the encoder emits `: comment`. */
|
|
454
|
+
readonly comment?: string;
|
|
414
455
|
}
|
|
415
456
|
|
|
416
457
|
/**
|
|
@@ -423,6 +464,15 @@ export function sseFrame(data: unknown, id?: string): SseFrame {
|
|
|
423
464
|
return id !== undefined ? { [sseFrameBrand]: true, data, id } : { [sseFrameBrand]: true, data };
|
|
424
465
|
}
|
|
425
466
|
|
|
467
|
+
/**
|
|
468
|
+
* SSE comment (`: keepalive`). Keeps a followed run from being idle-closed.
|
|
469
|
+
*
|
|
470
|
+
* @param text - Comment text
|
|
471
|
+
*/
|
|
472
|
+
export function sseComment(text: string): SseFrame {
|
|
473
|
+
return { [sseFrameBrand]: true, data: undefined, comment: text };
|
|
474
|
+
}
|
|
475
|
+
|
|
426
476
|
/**
|
|
427
477
|
* True when `value` is a branded SSE frame.
|
|
428
478
|
*
|
|
@@ -726,7 +776,7 @@ export interface Fx {
|
|
|
726
776
|
* @param flow - Flow name or handle
|
|
727
777
|
* @param input - Input payload
|
|
728
778
|
*/
|
|
729
|
-
call(flow:
|
|
779
|
+
call<F extends NamedRef>(flow: F, input?: FxCallInput<F>): FxCallResult<F>;
|
|
730
780
|
/**
|
|
731
781
|
* Query the Runs wide-event store (records `read` on `"runs"`).
|
|
732
782
|
* Requires a bound runs runtime and `effects.reads` including `"runs"`.
|
|
@@ -748,10 +798,6 @@ export interface Fx {
|
|
|
748
798
|
/**
|
|
749
799
|
* Send a provider-managed SMS OTP (records `send` on `sms-otp`).
|
|
750
800
|
*
|
|
751
|
-
* Vendor extra (Taqnyat Verify API) — requires a bound SMS driver that
|
|
752
|
-
* supports provider-managed OTP. Dry-run records would-have-fired without
|
|
753
|
-
* contacting the provider.
|
|
754
|
-
*
|
|
755
801
|
* @param opts - Recipient + requestId (+ lang / note / from)
|
|
756
802
|
*/
|
|
757
803
|
sendOtp(opts: FxSendOtpOptions): Promise<{ ok: true }>;
|
|
@@ -764,9 +810,6 @@ export interface Fx {
|
|
|
764
810
|
/**
|
|
765
811
|
* Deliver an app-owned OTP across declared channels (records `send` on `auth-otp`).
|
|
766
812
|
*
|
|
767
|
-
* Tier-2 only — uses Channel `deliverOtp` (sently FallbackTransport). Pass
|
|
768
|
-
* `only` for explicit user resend (single channel, no cross-medium failover).
|
|
769
|
-
*
|
|
770
813
|
* @param opts - Channels, templates, addresses, OTP data
|
|
771
814
|
*/
|
|
772
815
|
deliverOtp(opts: FxDeliverOtpOptions): Promise<{
|
|
@@ -781,6 +824,11 @@ export interface Fx {
|
|
|
781
824
|
* @param opts - Model routing opts
|
|
782
825
|
*/
|
|
783
826
|
ask(prompt: NamedRef, input?: unknown, opts?: FxAskOptions): Promise<Record<string, unknown>>;
|
|
827
|
+
ask(
|
|
828
|
+
prompt: NamedRef,
|
|
829
|
+
input: unknown,
|
|
830
|
+
opts: FxAskOptions & { readonly stream: true },
|
|
831
|
+
): AsyncIterable<AgUiEvent>;
|
|
784
832
|
/**
|
|
785
833
|
* Embed text via a named model (records `embed` — distinct from `ask`).
|
|
786
834
|
* Returns the vector; does not write an index.
|
|
@@ -808,13 +856,40 @@ export interface Fx {
|
|
|
808
856
|
* @param init - Standard `RequestInit`
|
|
809
857
|
*/
|
|
810
858
|
fetch(url: string | URL, init?: RequestInit): Promise<Response>;
|
|
859
|
+
/**
|
|
860
|
+
* Run one declared decision. `$` carries `how` and `audited`.
|
|
861
|
+
* Abstain mode makes each question value `T | null`.
|
|
862
|
+
*/
|
|
863
|
+
decide<T>(decision: { name: string }, input: unknown): Promise<T>;
|
|
864
|
+
/**
|
|
865
|
+
* Resolve a pending agent tool approval (records nothing until the tool runs).
|
|
866
|
+
*
|
|
867
|
+
* First resolution wins. A later call returns a conflict failure.
|
|
868
|
+
*/
|
|
869
|
+
readonly agent: {
|
|
870
|
+
/**
|
|
871
|
+
* @param id - Approval id from the interrupt
|
|
872
|
+
* @param opts - Optional replacement args
|
|
873
|
+
*/
|
|
874
|
+
approve(id: string, opts?: { readonly args?: unknown }): Promise<AgentApprovalResolveResult>;
|
|
875
|
+
/**
|
|
876
|
+
* @param id - Approval id from the interrupt
|
|
877
|
+
* @param opts - Denial reason fed back to the model
|
|
878
|
+
*/
|
|
879
|
+
deny(id: string, opts?: { readonly reason?: string }): Promise<AgentApprovalResolveResult>;
|
|
880
|
+
};
|
|
811
881
|
/**
|
|
812
882
|
* Run a bounded AI agent (records `ask`).
|
|
813
883
|
*
|
|
814
884
|
* @param agent - Agent name or handle
|
|
815
|
-
* @param input - Agent input (`{ message }` or string)
|
|
885
|
+
* @param input - Agent input (`{ message }`, `{ messages }`, or string)
|
|
816
886
|
*/
|
|
817
887
|
run(agent: NamedRef, input?: unknown): Promise<unknown>;
|
|
888
|
+
run(
|
|
889
|
+
agent: NamedRef,
|
|
890
|
+
input: unknown,
|
|
891
|
+
opts: { readonly stream: true; readonly threadId?: string },
|
|
892
|
+
): AsyncIterable<AgUiEvent>;
|
|
818
893
|
/**
|
|
819
894
|
* Stream model tokens (records `ask`). Returns an async iterable of chunks.
|
|
820
895
|
*
|
|
@@ -844,6 +919,8 @@ export interface Fx {
|
|
|
844
919
|
t(key: AppMessageKey, values?: MessageValues): string;
|
|
845
920
|
/** Active locale for {@link Fx.t} and default channel sends. */
|
|
846
921
|
readonly locale: string;
|
|
922
|
+
/** HTTP `Last-Event-ID` on this request, when the client sent one. */
|
|
923
|
+
readonly lastEventId?: string;
|
|
847
924
|
/** Generate a unique id (OKID). */
|
|
848
925
|
id(): string;
|
|
849
926
|
/** User-plane auth principal. */
|
|
@@ -1200,11 +1277,12 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1200
1277
|
externalOf?:
|
|
1201
1278
|
| EffectExternal
|
|
1202
1279
|
| ((result: T | undefined, error: unknown) => EffectExternal | undefined),
|
|
1280
|
+
stamp?: (result: T | undefined) => { readonly agentRunId?: string } | undefined,
|
|
1203
1281
|
): Promise<T> {
|
|
1204
1282
|
const signal = currentAbortSignal();
|
|
1205
1283
|
if (signal.aborted) throw abortError(signal.reason);
|
|
1206
1284
|
capability.assert(kind, resource);
|
|
1207
|
-
const execute = () => recordEffect(ledger, kind, resource, now, body, externalOf);
|
|
1285
|
+
const execute = () => recordEffect(ledger, kind, resource, now, body, externalOf, stamp);
|
|
1208
1286
|
if (journal) {
|
|
1209
1287
|
return journal.effect(kind, resource, execute);
|
|
1210
1288
|
}
|
|
@@ -1330,6 +1408,11 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1330
1408
|
gated,
|
|
1331
1409
|
capability,
|
|
1332
1410
|
ask: (model, input) => fx.ask(model, input),
|
|
1411
|
+
beginSignal: async () => {
|
|
1412
|
+
const bus = options.signalRuntime?.bus;
|
|
1413
|
+
if (!bus) return undefined;
|
|
1414
|
+
return bus.begin();
|
|
1415
|
+
},
|
|
1333
1416
|
});
|
|
1334
1417
|
}
|
|
1335
1418
|
|
|
@@ -1422,6 +1505,12 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1422
1505
|
|
|
1423
1506
|
const aiDisablesCache = options.aiRuntime?.autoCacheDisabled === true;
|
|
1424
1507
|
const telemetry = options.runTelemetry;
|
|
1508
|
+
if (telemetry) {
|
|
1509
|
+
lazyRequire<{ bindDecisionLabelTelemetry(telemetry: RunTelemetry): void }>(
|
|
1510
|
+
`${import.meta.dir}/../elements/ai/decisions`,
|
|
1511
|
+
["lab", "els"].join(""),
|
|
1512
|
+
).bindDecisionLabelTelemetry(telemetry);
|
|
1513
|
+
}
|
|
1425
1514
|
|
|
1426
1515
|
const cache: FxCache = {
|
|
1427
1516
|
async get<T = unknown>(key: string): Promise<T | undefined> {
|
|
@@ -1663,19 +1752,55 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1663
1752
|
},
|
|
1664
1753
|
};
|
|
1665
1754
|
|
|
1755
|
+
async function resolveAgentDecision(
|
|
1756
|
+
id: string,
|
|
1757
|
+
decision: {
|
|
1758
|
+
readonly decision: "approve" | "deny";
|
|
1759
|
+
readonly args?: unknown;
|
|
1760
|
+
readonly reason?: string;
|
|
1761
|
+
},
|
|
1762
|
+
): Promise<AgentApprovalResolveResult> {
|
|
1763
|
+
const runtime = options.aiRuntime;
|
|
1764
|
+
if (!runtime?.resolveApproval) return { ok: false, status: 404 };
|
|
1765
|
+
const approver = auth.userId ?? operator.id;
|
|
1766
|
+
return runtime.resolveApproval(
|
|
1767
|
+
id,
|
|
1768
|
+
{
|
|
1769
|
+
decision: decision.decision,
|
|
1770
|
+
tenant: tenant.id,
|
|
1771
|
+
...(decision.args !== undefined ? { args: decision.args } : {}),
|
|
1772
|
+
...(decision.reason !== undefined ? { reason: decision.reason } : {}),
|
|
1773
|
+
...(approver ? { approver } : {}),
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
auth: {
|
|
1777
|
+
userId: auth.userId,
|
|
1778
|
+
scopes: auth.scopes,
|
|
1779
|
+
verified: auth.verified,
|
|
1780
|
+
},
|
|
1781
|
+
operator: { id: operator.id },
|
|
1782
|
+
},
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1666
1786
|
const fx: Fx = {
|
|
1667
1787
|
store: storeHandle,
|
|
1668
1788
|
runs: runsSurface,
|
|
1669
1789
|
emit(signal: NamedRef, payload?: unknown, emitOptions?: SignalEmitOptions) {
|
|
1670
1790
|
const name = resolveName(signal);
|
|
1671
1791
|
return gated("emit", name, async () => {
|
|
1792
|
+
const merged: SignalEmitOptions = {
|
|
1793
|
+
...emitOptions,
|
|
1794
|
+
...(options.runId !== undefined && emitOptions?.parentRunId === undefined
|
|
1795
|
+
? { parentRunId: options.runId }
|
|
1796
|
+
: {}),
|
|
1797
|
+
};
|
|
1798
|
+
const pending = currentSignalTransaction();
|
|
1799
|
+
if (pending) {
|
|
1800
|
+
await pending.emit(name, payload, merged);
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1672
1803
|
if (options.signalRuntime) {
|
|
1673
|
-
const merged: SignalEmitOptions = {
|
|
1674
|
-
...emitOptions,
|
|
1675
|
-
...(options.runId !== undefined && emitOptions?.parentRunId === undefined
|
|
1676
|
-
? { parentRunId: options.runId }
|
|
1677
|
-
: {}),
|
|
1678
|
-
};
|
|
1679
1804
|
await options.signalRuntime.emit(name, payload, merged);
|
|
1680
1805
|
}
|
|
1681
1806
|
});
|
|
@@ -1701,7 +1826,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1701
1826
|
signalRuntime: options.signalRuntime,
|
|
1702
1827
|
}) as JsonStreamResult;
|
|
1703
1828
|
},
|
|
1704
|
-
call(flow, input) {
|
|
1829
|
+
call<F extends NamedRef>(flow: F, input?: FxCallInput<F>): FxCallResult<F> {
|
|
1705
1830
|
const name = resolveName(flow);
|
|
1706
1831
|
return gated("call", name, async () => {
|
|
1707
1832
|
if (isMcpToolRef(name)) {
|
|
@@ -1714,7 +1839,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1714
1839
|
return options.callHandler(name, input);
|
|
1715
1840
|
}
|
|
1716
1841
|
return undefined;
|
|
1717
|
-
})
|
|
1842
|
+
}) as FxCallResult<F>;
|
|
1718
1843
|
},
|
|
1719
1844
|
clock,
|
|
1720
1845
|
vault: vaultSurface,
|
|
@@ -1815,8 +1940,37 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1815
1940
|
return { ok: true as const, channel: result.channel };
|
|
1816
1941
|
});
|
|
1817
1942
|
},
|
|
1818
|
-
ask(prompt, input, opts) {
|
|
1943
|
+
ask: function (prompt, input, opts) {
|
|
1819
1944
|
const name = resolveName(prompt);
|
|
1945
|
+
if (opts?.stream) {
|
|
1946
|
+
return (async function* () {
|
|
1947
|
+
await gated("ask", name, async () => undefined);
|
|
1948
|
+
if (isDryRun()) {
|
|
1949
|
+
recordWouldHaveFired("ask", name);
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
if (!options.aiRuntime) {
|
|
1953
|
+
throw new Error(`fx.ask: AI runtime is not configured for prompt "${name}"`);
|
|
1954
|
+
}
|
|
1955
|
+
const local = new AbortController();
|
|
1956
|
+
const unlink = linkAbort(currentAbortSignal(), local);
|
|
1957
|
+
try {
|
|
1958
|
+
const events = await withAbortSignal(local.signal, () =>
|
|
1959
|
+
options.aiRuntime!.streamAsk(name, input, {
|
|
1960
|
+
via: opts.via?.map(resolveName),
|
|
1961
|
+
...(opts.timeout !== undefined ? { timeout: opts.timeout } : {}),
|
|
1962
|
+
tools: opts.tools?.map(resolveName),
|
|
1963
|
+
maxSteps: opts.maxSteps,
|
|
1964
|
+
callTool: (tool, toolInput) => fx.call(tool, toolInput),
|
|
1965
|
+
}),
|
|
1966
|
+
);
|
|
1967
|
+
yield* events;
|
|
1968
|
+
} finally {
|
|
1969
|
+
unlink();
|
|
1970
|
+
if (!local.signal.aborted) local.abort();
|
|
1971
|
+
}
|
|
1972
|
+
})();
|
|
1973
|
+
}
|
|
1820
1974
|
return gated(
|
|
1821
1975
|
"ask",
|
|
1822
1976
|
name,
|
|
@@ -1844,7 +1998,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1844
1998
|
},
|
|
1845
1999
|
() => options.aiRuntime?.lastExternal,
|
|
1846
2000
|
);
|
|
1847
|
-
},
|
|
2001
|
+
} as Fx["ask"],
|
|
1848
2002
|
embed(model, text) {
|
|
1849
2003
|
const name = resolveName(model);
|
|
1850
2004
|
return gated(
|
|
@@ -1890,29 +2044,158 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1890
2044
|
fetch(url, init) {
|
|
1891
2045
|
return loadFxFetch().runFxFetch(gated, url, init);
|
|
1892
2046
|
},
|
|
1893
|
-
|
|
2047
|
+
decide<T>(decision: { name: string }, input: unknown) {
|
|
2048
|
+
return loadFxDecide().runFxDecide({
|
|
2049
|
+
gated,
|
|
2050
|
+
journal: options.journal,
|
|
2051
|
+
signal: currentAbortSignal(),
|
|
2052
|
+
now,
|
|
2053
|
+
runId: options.runId,
|
|
2054
|
+
tenantId: tenant.id,
|
|
2055
|
+
getSecret: async (secretName) => {
|
|
2056
|
+
const value = await gatedSecret(secretName, async () => secrets[secretName]);
|
|
2057
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
2058
|
+
},
|
|
2059
|
+
decision,
|
|
2060
|
+
input,
|
|
2061
|
+
}) as Promise<T>;
|
|
2062
|
+
},
|
|
2063
|
+
agent: {
|
|
2064
|
+
approve(id, opts) {
|
|
2065
|
+
return resolveAgentDecision(id, { decision: "approve", args: opts?.args });
|
|
2066
|
+
},
|
|
2067
|
+
deny(id, opts) {
|
|
2068
|
+
return resolveAgentDecision(id, { decision: "deny", reason: opts?.reason });
|
|
2069
|
+
},
|
|
2070
|
+
},
|
|
2071
|
+
run: function (agent, input, opts?: { readonly stream?: boolean; readonly threadId?: string }) {
|
|
1894
2072
|
const name = resolveName(agent);
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2073
|
+
const turn = agentTurn(input);
|
|
2074
|
+
if (opts?.stream) {
|
|
2075
|
+
let inner: (AsyncIterable<AgUiEvent> & { readonly result?: Promise<unknown> }) | undefined;
|
|
2076
|
+
let heldRunId: string | undefined;
|
|
2077
|
+
let markRun: () => void = () => undefined;
|
|
2078
|
+
const heldReady = new Promise<void>((resolve) => {
|
|
2079
|
+
markRun = resolve;
|
|
2080
|
+
});
|
|
2081
|
+
const gen = (async function* () {
|
|
2082
|
+
if (!options.aiRuntime) {
|
|
2083
|
+
await gated("ask", name, async () => {
|
|
2084
|
+
throw new Error(`fx.run: AI runtime is not configured for agent "${name}"`);
|
|
2085
|
+
});
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
const local = new AbortController();
|
|
2089
|
+
const unlink = linkAbort(currentAbortSignal(), local);
|
|
2090
|
+
try {
|
|
2091
|
+
await withAbortSignal(local.signal, async () => {
|
|
2092
|
+
// The inner tool loop journals each step. Recording the whole
|
|
2093
|
+
// stream as one ask effect would replay the run id and skip
|
|
2094
|
+
// the parked tool.
|
|
2095
|
+
capability.assert("ask", name);
|
|
2096
|
+
const events = await options.aiRuntime!.streamAgent(name, {
|
|
2097
|
+
...turn,
|
|
2098
|
+
...(opts.threadId !== undefined ? { threadId: opts.threadId } : {}),
|
|
2099
|
+
gates: (options.rlsGateNames ?? []).filter((gate) => gate !== "public"),
|
|
2100
|
+
userId: auth.userId,
|
|
2101
|
+
operatorId: operator.id,
|
|
2102
|
+
...(options.journal ? { journal: options.journal } : {}),
|
|
2103
|
+
...(options.flow !== undefined ? { flow: options.flow } : {}),
|
|
2104
|
+
tenantId: tenant.id,
|
|
2105
|
+
auth: {
|
|
2106
|
+
userId: auth.userId,
|
|
2107
|
+
scopes: auth.scopes,
|
|
2108
|
+
verified: auth.verified,
|
|
2109
|
+
},
|
|
2110
|
+
callTool: (tool, toolInput) => fx.call(tool, toolInput),
|
|
2111
|
+
recordCall: (tool, runId) => {
|
|
2112
|
+
void gated(
|
|
2113
|
+
"call",
|
|
2114
|
+
tool,
|
|
2115
|
+
async () => runId,
|
|
2116
|
+
undefined,
|
|
2117
|
+
(id) =>
|
|
2118
|
+
typeof id === "string" && id.length > 0 ? { agentRunId: id } : undefined,
|
|
2119
|
+
);
|
|
2120
|
+
},
|
|
2121
|
+
onRunId(id) {
|
|
2122
|
+
heldRunId = id;
|
|
2123
|
+
markRun();
|
|
2124
|
+
},
|
|
2125
|
+
});
|
|
2126
|
+
inner = events as AsyncIterable<AgUiEvent> & {
|
|
2127
|
+
readonly result?: Promise<unknown>;
|
|
2128
|
+
};
|
|
2129
|
+
const settled = inner.result?.then(
|
|
2130
|
+
() => undefined,
|
|
2131
|
+
() => undefined,
|
|
2132
|
+
);
|
|
2133
|
+
await Promise.race([heldReady, settled ?? Promise.resolve()]);
|
|
2134
|
+
await recordEffect(
|
|
2135
|
+
ledger,
|
|
2136
|
+
"ask",
|
|
2137
|
+
name,
|
|
2138
|
+
now,
|
|
2139
|
+
async () => heldRunId,
|
|
2140
|
+
undefined,
|
|
2141
|
+
(id) => (typeof id === "string" && id.length > 0 ? { agentRunId: id } : undefined),
|
|
2142
|
+
);
|
|
2143
|
+
});
|
|
2144
|
+
if (inner) yield* inner;
|
|
2145
|
+
} finally {
|
|
2146
|
+
unlink();
|
|
2147
|
+
if (!local.signal.aborted) local.abort();
|
|
2148
|
+
}
|
|
2149
|
+
})();
|
|
2150
|
+
Object.defineProperty(gen, "result", {
|
|
2151
|
+
enumerable: true,
|
|
2152
|
+
get() {
|
|
2153
|
+
return inner?.result;
|
|
2154
|
+
},
|
|
2155
|
+
});
|
|
2156
|
+
return gen;
|
|
2157
|
+
}
|
|
2158
|
+
return gated(
|
|
2159
|
+
"ask",
|
|
2160
|
+
name,
|
|
2161
|
+
async () => {
|
|
2162
|
+
if (options.aiRuntime) {
|
|
2163
|
+
return options.aiRuntime.runAgent(name, {
|
|
2164
|
+
...turn,
|
|
2165
|
+
gates: (options.rlsGateNames ?? []).filter((gate) => gate !== "public"),
|
|
1906
2166
|
userId: auth.userId,
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2167
|
+
operatorId: operator.id,
|
|
2168
|
+
...(options.journal ? { journal: options.journal } : {}),
|
|
2169
|
+
...(options.flow !== undefined ? { flow: options.flow } : {}),
|
|
2170
|
+
tenantId: tenant.id,
|
|
2171
|
+
auth: {
|
|
2172
|
+
userId: auth.userId,
|
|
2173
|
+
scopes: auth.scopes,
|
|
2174
|
+
verified: auth.verified,
|
|
2175
|
+
},
|
|
2176
|
+
callTool: (tool, toolInput) => fx.call(tool, toolInput),
|
|
2177
|
+
recordCall: (tool, runId) => {
|
|
2178
|
+
void gated(
|
|
2179
|
+
"call",
|
|
2180
|
+
tool,
|
|
2181
|
+
async () => runId,
|
|
2182
|
+
undefined,
|
|
2183
|
+
(id) =>
|
|
2184
|
+
typeof id === "string" && id.length > 0 ? { agentRunId: id } : undefined,
|
|
2185
|
+
);
|
|
2186
|
+
},
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
return { ok: true, steps: 0, denials: [], output: input };
|
|
2190
|
+
},
|
|
2191
|
+
undefined,
|
|
2192
|
+
(result) => {
|
|
2193
|
+
if (!result || typeof result !== "object" || !("runId" in result)) return undefined;
|
|
2194
|
+
const id = (result as { runId?: unknown }).runId;
|
|
2195
|
+
return typeof id === "string" && id.length > 0 ? { agentRunId: id } : undefined;
|
|
2196
|
+
},
|
|
2197
|
+
);
|
|
2198
|
+
} as Fx["run"],
|
|
1916
2199
|
stream(model, opts) {
|
|
1917
2200
|
const name = resolveName(model);
|
|
1918
2201
|
const chunks = (async function* () {
|
|
@@ -1957,6 +2240,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1957
2240
|
});
|
|
1958
2241
|
},
|
|
1959
2242
|
locale,
|
|
2243
|
+
lastEventId: options.lastEventId,
|
|
1960
2244
|
id() {
|
|
1961
2245
|
return okid();
|
|
1962
2246
|
},
|
|
@@ -1983,12 +2267,22 @@ export function createFxContext(options: CreateFxOptions): FxContext {
|
|
|
1983
2267
|
with: jsonWith,
|
|
1984
2268
|
withQuery: jsonWithQuery,
|
|
1985
2269
|
stream(chunks) {
|
|
1986
|
-
|
|
2270
|
+
const carrier = {
|
|
1987
2271
|
[jsonResultBrand]: true,
|
|
1988
2272
|
kind: "stream" as const,
|
|
1989
2273
|
status: 200 as const,
|
|
1990
2274
|
chunks,
|
|
1991
2275
|
};
|
|
2276
|
+
Object.defineProperty(carrier, "result", {
|
|
2277
|
+
enumerable: true,
|
|
2278
|
+
get() {
|
|
2279
|
+
if (chunks && typeof chunks === "object" && "result" in chunks) {
|
|
2280
|
+
return (chunks as { readonly result?: Promise<unknown> }).result;
|
|
2281
|
+
}
|
|
2282
|
+
return undefined;
|
|
2283
|
+
},
|
|
2284
|
+
});
|
|
2285
|
+
return carrier as JsonStreamResult;
|
|
1992
2286
|
},
|
|
1993
2287
|
},
|
|
1994
2288
|
async step<T>(name: string, fn: () => T | Promise<T>, opts?: StepOptions<T>): Promise<T> {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP wire-frame capture redacts credentials and keeps the rest.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import {
|
|
7
|
+
HTTP_FRAME_REDACTED,
|
|
8
|
+
captureHttpFrame,
|
|
9
|
+
redactHttpFrame,
|
|
10
|
+
requestForHttpInvoke,
|
|
11
|
+
} from "./http-frame.ts";
|
|
12
|
+
|
|
13
|
+
describe("requestForHttpInvoke", () => {
|
|
14
|
+
test("GET puts scalar input on the query string and keeps accept", () => {
|
|
15
|
+
const request = requestForHttpInvoke(
|
|
16
|
+
{ method: "GET", path: "/tasks/:id" },
|
|
17
|
+
{ pathParams: { id: "tsk_1" }, input: { id: "tsk_1", limit: 20 } },
|
|
18
|
+
);
|
|
19
|
+
expect(request.method).toBe("GET");
|
|
20
|
+
expect(new URL(request.url).pathname).toBe("/tasks/tsk_1");
|
|
21
|
+
expect(new URL(request.url).searchParams.get("limit")).toBe("20");
|
|
22
|
+
expect(new URL(request.url).searchParams.has("id")).toBe(false);
|
|
23
|
+
expect(request.headers.get("accept")).toBe("application/json");
|
|
24
|
+
expect(request.headers.get("x-oke-client")).toBe("console");
|
|
25
|
+
expect(request.headers.get("content-type")).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("captureHttpFrame", () => {
|
|
30
|
+
test("keeps method, path, query, and headers and redacts credentials", () => {
|
|
31
|
+
const request = new Request("http://localhost/tasks?limit=20&token=sekrit", {
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers: {
|
|
34
|
+
accept: "application/json",
|
|
35
|
+
authorization: "Bearer super-secret",
|
|
36
|
+
cookie: "oke_session=abc",
|
|
37
|
+
"x-request-id": "req_1",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const response = new Response(JSON.stringify({ ok: true }), {
|
|
41
|
+
status: 200,
|
|
42
|
+
headers: {
|
|
43
|
+
"content-type": "application/json",
|
|
44
|
+
"set-cookie": "oke_session=next; HttpOnly",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(captureHttpFrame(request, response)).toEqual({
|
|
49
|
+
request: {
|
|
50
|
+
method: "GET",
|
|
51
|
+
path: "/tasks",
|
|
52
|
+
query: { limit: "20", token: HTTP_FRAME_REDACTED },
|
|
53
|
+
headers: {
|
|
54
|
+
accept: "application/json",
|
|
55
|
+
authorization: HTTP_FRAME_REDACTED,
|
|
56
|
+
cookie: HTTP_FRAME_REDACTED,
|
|
57
|
+
"x-request-id": "req_1",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
response: {
|
|
61
|
+
status: 200,
|
|
62
|
+
headers: {
|
|
63
|
+
"content-type": "application/json",
|
|
64
|
+
"set-cookie": HTTP_FRAME_REDACTED,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("redactHttpFrame masks a frame that skipped capture", () => {
|
|
71
|
+
expect(
|
|
72
|
+
redactHttpFrame({
|
|
73
|
+
request: {
|
|
74
|
+
method: "POST",
|
|
75
|
+
path: "/tasks",
|
|
76
|
+
query: {},
|
|
77
|
+
headers: { authorization: "Bearer leak", host: "localhost" },
|
|
78
|
+
},
|
|
79
|
+
response: { status: 201, headers: { "x-api-key": "leak" } },
|
|
80
|
+
}),
|
|
81
|
+
).toEqual({
|
|
82
|
+
request: {
|
|
83
|
+
method: "POST",
|
|
84
|
+
path: "/tasks",
|
|
85
|
+
query: {},
|
|
86
|
+
headers: { authorization: HTTP_FRAME_REDACTED, host: "localhost" },
|
|
87
|
+
},
|
|
88
|
+
response: {
|
|
89
|
+
status: 201,
|
|
90
|
+
headers: { "x-api-key": HTTP_FRAME_REDACTED },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|