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
|
@@ -6,6 +6,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
6
6
|
import { withAbortSignal } from "../kernel/abort-scope.ts";
|
|
7
7
|
import { createFx, createFxContext } from "../kernel/fx.ts";
|
|
8
8
|
import { ai, createAiRuntime } from "../elements/ai.ts";
|
|
9
|
+
import { openAnthropic } from "./ai-anthropic.ts";
|
|
9
10
|
import { openOpenaiCompatible } from "./ai-openai-compatible.ts";
|
|
10
11
|
import { mockAiDriver } from "./ai-mock.ts";
|
|
11
12
|
|
|
@@ -44,6 +45,29 @@ describe("openai-compatible SSE stream", () => {
|
|
|
44
45
|
if (chunk.text) parts.push(chunk.text);
|
|
45
46
|
}
|
|
46
47
|
expect(parts.join("")).toBe("Hello");
|
|
48
|
+
const bodies: unknown[] = [];
|
|
49
|
+
const formatFetch = Object.assign(
|
|
50
|
+
async (_input: string | URL | Request, init?: RequestInit) => {
|
|
51
|
+
bodies.push(JSON.parse(String(init?.body)));
|
|
52
|
+
return new Response(sse, { status: 200, headers: { "content-type": "text/event-stream" } });
|
|
53
|
+
},
|
|
54
|
+
{ preconnect: () => {} },
|
|
55
|
+
) as typeof fetch;
|
|
56
|
+
const formatted = await openOpenaiCompatible({
|
|
57
|
+
apiKey: "sk-test",
|
|
58
|
+
baseUrl: "https://example.test/v1",
|
|
59
|
+
fetch: formatFetch,
|
|
60
|
+
});
|
|
61
|
+
for await (const _chunk of formatted.stream!({
|
|
62
|
+
messages: [{ role: "user", content: "hi" }],
|
|
63
|
+
responseFormat: {
|
|
64
|
+
type: "json_schema",
|
|
65
|
+
json_schema: { name: "out", schema: { type: "object" } },
|
|
66
|
+
},
|
|
67
|
+
})) {
|
|
68
|
+
// drain
|
|
69
|
+
}
|
|
70
|
+
expect(bodies[0]).toMatchObject({ response_format: { type: "json_object" } });
|
|
47
71
|
|
|
48
72
|
const ac = new AbortController();
|
|
49
73
|
const iter = client.stream!({
|
|
@@ -58,6 +82,44 @@ describe("openai-compatible SSE stream", () => {
|
|
|
58
82
|
});
|
|
59
83
|
});
|
|
60
84
|
|
|
85
|
+
describe("anthropic messages stream", () => {
|
|
86
|
+
test("text deltas, input_json_delta, and message_delta usage", async () => {
|
|
87
|
+
const sse = [
|
|
88
|
+
'data: {"type":"message_start","message":{"usage":{"input_tokens":4,"output_tokens":1}}}',
|
|
89
|
+
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Ok"}}',
|
|
90
|
+
'data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_1","name":"orders.get","input":{}}}',
|
|
91
|
+
'data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\\"id\\":"}}',
|
|
92
|
+
'data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\\"14\\"}"}}',
|
|
93
|
+
'data: {"type":"message_delta","delta":{"stop_reason":"tool_use"},"usage":{"output_tokens":6}}',
|
|
94
|
+
'data: {"type":"message_stop"}',
|
|
95
|
+
"",
|
|
96
|
+
].join("\n");
|
|
97
|
+
const fetchFn = Object.assign(
|
|
98
|
+
async () =>
|
|
99
|
+
new Response(sse, { status: 200, headers: { "content-type": "text/event-stream" } }),
|
|
100
|
+
{ preconnect: () => {} },
|
|
101
|
+
) as typeof fetch;
|
|
102
|
+
const client = await openAnthropic({
|
|
103
|
+
apiKey: "sk-test",
|
|
104
|
+
fetch: fetchFn,
|
|
105
|
+
});
|
|
106
|
+
const texts: string[] = [];
|
|
107
|
+
const args: string[] = [];
|
|
108
|
+
let usage: { inputTokens?: number; outputTokens?: number } | undefined;
|
|
109
|
+
for await (const chunk of client.stream!({
|
|
110
|
+
messages: [{ role: "user", content: "status" }],
|
|
111
|
+
tools: [{ name: "orders.get" }],
|
|
112
|
+
})) {
|
|
113
|
+
if (chunk.text) texts.push(chunk.text);
|
|
114
|
+
if (chunk.toolCall?.argumentsDelta) args.push(chunk.toolCall.argumentsDelta);
|
|
115
|
+
if (chunk.usage) usage = chunk.usage;
|
|
116
|
+
}
|
|
117
|
+
expect(texts.join("")).toBe("Ok");
|
|
118
|
+
expect(args.join("")).toBe('{"id":"14"}');
|
|
119
|
+
expect(usage).toEqual({ inputTokens: 4, outputTokens: 6 });
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
61
123
|
describe("fx.stream uses ambient AbortSignal", () => {
|
|
62
124
|
test("race abort cancels in-flight stream fetch", async () => {
|
|
63
125
|
let sawAbort = false;
|
package/src/drivers/ai-types.ts
CHANGED
|
@@ -66,10 +66,30 @@ export interface AiCompleteResult {
|
|
|
66
66
|
readonly external?: DriverExternal;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/** One tool-call fragment inside a model stream. */
|
|
70
|
+
export interface AiStreamToolCallDelta {
|
|
71
|
+
/** Provider index for this call within the turn. */
|
|
72
|
+
readonly index: number;
|
|
73
|
+
readonly id?: string;
|
|
74
|
+
readonly name?: string;
|
|
75
|
+
/** Partial JSON arguments. Empty when the call is only announced. */
|
|
76
|
+
readonly argumentsDelta?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
/** One streamed token / delta from a model. */
|
|
70
80
|
export interface AiStreamChunk {
|
|
71
81
|
readonly text: string;
|
|
72
82
|
readonly done?: boolean;
|
|
83
|
+
readonly toolCall?: AiStreamToolCallDelta;
|
|
84
|
+
readonly usage?: {
|
|
85
|
+
readonly inputTokens?: number;
|
|
86
|
+
readonly outputTokens?: number;
|
|
87
|
+
readonly cost?: number;
|
|
88
|
+
};
|
|
89
|
+
/** Provider payload. Present on the terminal chunk when the driver has one. */
|
|
90
|
+
readonly raw?: unknown;
|
|
91
|
+
/** Egress identity, matching {@link AiCompleteResult.external}. */
|
|
92
|
+
readonly external?: DriverExternal;
|
|
73
93
|
}
|
|
74
94
|
|
|
75
95
|
/** Embedding request. */
|
|
@@ -7,6 +7,22 @@
|
|
|
7
7
|
* completed journal steps replay instead of re-running).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
createPostgresIdempotencyStore,
|
|
12
|
+
IDEM_ATTACH_SQL,
|
|
13
|
+
IDEM_COMPLETE_SQL,
|
|
14
|
+
IDEM_DELETE_EXPIRED_ONE_SQL,
|
|
15
|
+
IDEM_FORFEIT_SQL,
|
|
16
|
+
IDEM_INSERT_SQL,
|
|
17
|
+
IDEM_PURGE_SQL,
|
|
18
|
+
IDEM_RECLAIM_SQL,
|
|
19
|
+
IDEM_REMOVE_SQL,
|
|
20
|
+
IDEM_RENEW_SQL,
|
|
21
|
+
IDEM_SELECT_SQL,
|
|
22
|
+
} from "../kernel/idempotency-store.ts";
|
|
23
|
+
import type { DecisionLabelStore } from "../kernel/decision-label-store.ts";
|
|
24
|
+
import type { AgentEventStore } from "../kernel/agent-event-store.ts";
|
|
25
|
+
import { lazyRequire } from "../kernel/lazy-require.ts";
|
|
10
26
|
import {
|
|
11
27
|
JOURNAL_DEFAULT_LEASE_MS,
|
|
12
28
|
type JournalEntry,
|
|
@@ -199,6 +215,26 @@ async function ensureSchema(sql: PostgresJournalSql): Promise<void> {
|
|
|
199
215
|
await sql.exec(
|
|
200
216
|
`CREATE INDEX IF NOT EXISTS oke_journal_runs_lease ON oke_journal_runs (status, lease_expires_at)`,
|
|
201
217
|
);
|
|
218
|
+
await sql.exec(`CREATE TABLE IF NOT EXISTS oke_idempotency (
|
|
219
|
+
tenant TEXT NOT NULL DEFAULT '',
|
|
220
|
+
principal TEXT NOT NULL,
|
|
221
|
+
flow TEXT NOT NULL,
|
|
222
|
+
key TEXT NOT NULL,
|
|
223
|
+
fingerprint TEXT NOT NULL,
|
|
224
|
+
status TEXT NOT NULL,
|
|
225
|
+
claim_token TEXT NOT NULL,
|
|
226
|
+
lease_expires_at BIGINT NOT NULL,
|
|
227
|
+
run_id TEXT,
|
|
228
|
+
response_status INTEGER,
|
|
229
|
+
response_headers TEXT,
|
|
230
|
+
response_body TEXT,
|
|
231
|
+
created_at BIGINT NOT NULL,
|
|
232
|
+
expires_at BIGINT NOT NULL,
|
|
233
|
+
PRIMARY KEY (tenant, principal, flow, key)
|
|
234
|
+
)`);
|
|
235
|
+
await sql.exec(
|
|
236
|
+
`CREATE INDEX IF NOT EXISTS oke_idempotency_expires ON oke_idempotency (expires_at)`,
|
|
237
|
+
);
|
|
202
238
|
}
|
|
203
239
|
|
|
204
240
|
/**
|
|
@@ -208,9 +244,63 @@ export function createPostgresJournalFake(): PostgresJournalSql & {
|
|
|
208
244
|
/** Force-kill mid-transaction (drops uncommitted state). */
|
|
209
245
|
killActiveTransaction(): void;
|
|
210
246
|
} {
|
|
211
|
-
type
|
|
247
|
+
type IdemDbRow = {
|
|
248
|
+
tenant: string;
|
|
249
|
+
principal: string;
|
|
250
|
+
flow: string;
|
|
251
|
+
key: string;
|
|
252
|
+
fingerprint: string;
|
|
253
|
+
status: string;
|
|
254
|
+
claim_token: string;
|
|
255
|
+
lease_expires_at: number;
|
|
256
|
+
run_id: string | null;
|
|
257
|
+
response_status: number | null;
|
|
258
|
+
response_headers: string | null;
|
|
259
|
+
response_body: string | null;
|
|
260
|
+
created_at: number;
|
|
261
|
+
expires_at: number;
|
|
262
|
+
};
|
|
263
|
+
type LabelDbRow = {
|
|
264
|
+
decision_id: string;
|
|
265
|
+
question: string;
|
|
266
|
+
value: string;
|
|
267
|
+
propensity: number;
|
|
268
|
+
reviewer: string;
|
|
269
|
+
locale: string | null;
|
|
270
|
+
model: string | null;
|
|
271
|
+
tenant: string | null;
|
|
272
|
+
score: number | null;
|
|
273
|
+
loss: number | null;
|
|
274
|
+
raw: string | null;
|
|
275
|
+
at: number;
|
|
276
|
+
input: string | null;
|
|
277
|
+
review_id?: string | null;
|
|
278
|
+
};
|
|
279
|
+
type State = {
|
|
280
|
+
rows: JournalDbRow[];
|
|
281
|
+
idem: IdemDbRow[];
|
|
282
|
+
labels: LabelDbRow[];
|
|
283
|
+
drift: { decision_id: string; suspended: number; certified_at: number }[];
|
|
284
|
+
candidates: { decision_id: string; body: string }[];
|
|
285
|
+
agentRuns: { run_id: string; header: string; sweep_claim_at: number | null }[];
|
|
286
|
+
agentEvents: {
|
|
287
|
+
run_id: string;
|
|
288
|
+
seq: number;
|
|
289
|
+
event: string;
|
|
290
|
+
event_type: string | null;
|
|
291
|
+
event_name: string | null;
|
|
292
|
+
}[];
|
|
293
|
+
};
|
|
212
294
|
|
|
213
|
-
let committed: State = {
|
|
295
|
+
let committed: State = {
|
|
296
|
+
rows: [],
|
|
297
|
+
idem: [],
|
|
298
|
+
labels: [],
|
|
299
|
+
drift: [],
|
|
300
|
+
candidates: [],
|
|
301
|
+
agentRuns: [],
|
|
302
|
+
agentEvents: [],
|
|
303
|
+
};
|
|
214
304
|
let active: { state: State; locked: Set<string>; done: boolean } | null = null;
|
|
215
305
|
/** Run ids held by other active transactions (SKIP LOCKED). */
|
|
216
306
|
const heldByTxn = new Set<string>();
|
|
@@ -222,7 +312,24 @@ export function createPostgresJournalFake(): PostgresJournalSql & {
|
|
|
222
312
|
}
|
|
223
313
|
|
|
224
314
|
function cloneState(s: State): State {
|
|
225
|
-
return {
|
|
315
|
+
return {
|
|
316
|
+
rows: s.rows.map((r) => ({ ...r })),
|
|
317
|
+
idem: s.idem.map((r) => ({ ...r })),
|
|
318
|
+
labels: s.labels.map((r) => ({ ...r })),
|
|
319
|
+
drift: s.drift.map((r) => ({ ...r })),
|
|
320
|
+
candidates: s.candidates.map((r) => ({ ...r })),
|
|
321
|
+
agentRuns: s.agentRuns.map((r) => ({ ...r })),
|
|
322
|
+
agentEvents: s.agentEvents.map((r) => ({ ...r })),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function idemPk(row: IdemDbRow, params: readonly unknown[], offset = 0): boolean {
|
|
327
|
+
return (
|
|
328
|
+
row.tenant === String(params[offset]) &&
|
|
329
|
+
row.principal === String(params[offset + 1]) &&
|
|
330
|
+
row.flow === String(params[offset + 2]) &&
|
|
331
|
+
row.key === String(params[offset + 3])
|
|
332
|
+
);
|
|
226
333
|
}
|
|
227
334
|
|
|
228
335
|
function claimable(r: JournalDbRow, holder: string, cutoff: number): boolean {
|
|
@@ -309,6 +416,71 @@ export function createPostgresJournalFake(): PostgresJournalSql & {
|
|
|
309
416
|
return state.rows.map((r) => ({ ...r }));
|
|
310
417
|
}
|
|
311
418
|
|
|
419
|
+
if (text === IDEM_SELECT_SQL) {
|
|
420
|
+
return state.idem
|
|
421
|
+
.filter((row) => idemPk(row, params))
|
|
422
|
+
.map((row) => ({ ...row }) as Record<string, unknown>);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (/FROM\s+oke_decision_labels/i.test(text)) {
|
|
426
|
+
let labels = state.labels;
|
|
427
|
+
let arg = 0;
|
|
428
|
+
if (/decision_id\s*=\s*\?/i.test(text)) {
|
|
429
|
+
const id = String(params[arg++] ?? "");
|
|
430
|
+
labels = labels.filter((row) => row.decision_id === id);
|
|
431
|
+
}
|
|
432
|
+
if (/tenant\s+IS\s+NOT\s+DISTINCT\s+FROM\s+\?/i.test(text)) {
|
|
433
|
+
const tenant = params[arg] ?? null;
|
|
434
|
+
labels = labels.filter((row) => row.tenant === tenant);
|
|
435
|
+
}
|
|
436
|
+
return labels.map((row) => ({ ...row }));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (/FROM\s+oke_decision_drift/i.test(text)) {
|
|
440
|
+
return state.drift.map((row) => ({ ...row }));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (/FROM\s+oke_decision_candidates/i.test(text)) {
|
|
444
|
+
if (/decision_id\s*=\s*\?/i.test(text)) {
|
|
445
|
+
const id = String(params[0] ?? "");
|
|
446
|
+
return state.candidates
|
|
447
|
+
.filter((row) => row.decision_id === id)
|
|
448
|
+
.map((row) => ({ ...row }));
|
|
449
|
+
}
|
|
450
|
+
return state.candidates.map((row) => ({ ...row }));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (/FROM\s+oke_agent_run\b/i.test(text)) {
|
|
454
|
+
if (!/WHERE/i.test(text)) return state.agentRuns.map((row) => ({ ...row }));
|
|
455
|
+
const id = String(params[0] ?? "");
|
|
456
|
+
return state.agentRuns.filter((row) => row.run_id === id).map((row) => ({ ...row }));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (/FROM\s+oke_agent_event\b/i.test(text)) {
|
|
460
|
+
const id = String(params[0] ?? "");
|
|
461
|
+
if (/MAX\s*\(\s*seq\s*\)/i.test(text)) {
|
|
462
|
+
const max = state.agentEvents
|
|
463
|
+
.filter((row) => row.run_id === id)
|
|
464
|
+
.reduce((highest, row) => Math.max(highest, row.seq), 0);
|
|
465
|
+
return [{ max_seq: max }];
|
|
466
|
+
}
|
|
467
|
+
if (/event_type\s*=/i.test(text)) {
|
|
468
|
+
const eventType = String(params[1] ?? "");
|
|
469
|
+
const eventName = String(params[2] ?? "");
|
|
470
|
+
return state.agentEvents
|
|
471
|
+
.filter(
|
|
472
|
+
(row) =>
|
|
473
|
+
row.run_id === id && row.event_type === eventType && row.event_name === eventName,
|
|
474
|
+
)
|
|
475
|
+
.map((row) => ({ ...row }));
|
|
476
|
+
}
|
|
477
|
+
const after = /seq\s*>\s*\?/i.test(text) ? Number(params[1] ?? 0) : 0;
|
|
478
|
+
return state.agentEvents
|
|
479
|
+
.filter((row) => row.run_id === id && row.seq > after)
|
|
480
|
+
.sort((a, b) => a.seq - b.seq)
|
|
481
|
+
.map((row) => ({ ...row }));
|
|
482
|
+
}
|
|
483
|
+
|
|
312
484
|
throw new Error(`postgres journal fake: unsupported query: ${sql}`);
|
|
313
485
|
},
|
|
314
486
|
async exec(sql, params = []) {
|
|
@@ -388,6 +560,209 @@ export function createPostgresJournalFake(): PostgresJournalSql & {
|
|
|
388
560
|
return { changes: 0 };
|
|
389
561
|
}
|
|
390
562
|
|
|
563
|
+
if (text === IDEM_PURGE_SQL) {
|
|
564
|
+
const now = Number(params[0]);
|
|
565
|
+
const before = state.idem.length;
|
|
566
|
+
state.idem = state.idem.filter((row) => row.expires_at > now);
|
|
567
|
+
return { changes: before - state.idem.length };
|
|
568
|
+
}
|
|
569
|
+
if (text === IDEM_DELETE_EXPIRED_ONE_SQL) {
|
|
570
|
+
const now = Number(params[4]);
|
|
571
|
+
const idx = state.idem.findIndex((row) => idemPk(row, params) && row.expires_at <= now);
|
|
572
|
+
if (idx < 0) return { changes: 0 };
|
|
573
|
+
state.idem.splice(idx, 1);
|
|
574
|
+
return { changes: 1 };
|
|
575
|
+
}
|
|
576
|
+
if (text === IDEM_INSERT_SQL) {
|
|
577
|
+
if (state.idem.some((row) => idemPk(row, params))) return { changes: 0 };
|
|
578
|
+
state.idem.push({
|
|
579
|
+
tenant: String(params[0]),
|
|
580
|
+
principal: String(params[1]),
|
|
581
|
+
flow: String(params[2]),
|
|
582
|
+
key: String(params[3]),
|
|
583
|
+
fingerprint: String(params[4]),
|
|
584
|
+
status: "in_progress",
|
|
585
|
+
claim_token: String(params[5]),
|
|
586
|
+
lease_expires_at: Number(params[6]),
|
|
587
|
+
run_id: null,
|
|
588
|
+
response_status: null,
|
|
589
|
+
response_headers: null,
|
|
590
|
+
response_body: null,
|
|
591
|
+
created_at: Number(params[7]),
|
|
592
|
+
expires_at: Number(params[8]),
|
|
593
|
+
});
|
|
594
|
+
return { changes: 1 };
|
|
595
|
+
}
|
|
596
|
+
if (text === IDEM_RECLAIM_SQL) {
|
|
597
|
+
const row = state.idem.find(
|
|
598
|
+
(candidate) =>
|
|
599
|
+
idemPk(candidate, params, 2) && candidate.claim_token === String(params[6]),
|
|
600
|
+
);
|
|
601
|
+
if (row === undefined || row.status !== "in_progress") return { changes: 0 };
|
|
602
|
+
row.claim_token = String(params[0]);
|
|
603
|
+
row.lease_expires_at = Number(params[1]);
|
|
604
|
+
return { changes: 1 };
|
|
605
|
+
}
|
|
606
|
+
if (text === IDEM_COMPLETE_SQL) {
|
|
607
|
+
const row = state.idem.find(
|
|
608
|
+
(candidate) =>
|
|
609
|
+
idemPk(candidate, params, 4) && candidate.claim_token === String(params[8]),
|
|
610
|
+
);
|
|
611
|
+
if (row === undefined) return { changes: 0 };
|
|
612
|
+
row.status = "completed";
|
|
613
|
+
row.response_status = Number(params[0]);
|
|
614
|
+
row.response_headers = params[1] === null ? null : String(params[1]);
|
|
615
|
+
row.response_body = params[2] === null ? null : String(params[2]);
|
|
616
|
+
if (params[3] !== null && params[3] !== undefined) row.run_id = String(params[3]);
|
|
617
|
+
return { changes: 1 };
|
|
618
|
+
}
|
|
619
|
+
if (text === IDEM_REMOVE_SQL) {
|
|
620
|
+
const idx = state.idem.findIndex(
|
|
621
|
+
(candidate) => idemPk(candidate, params) && candidate.claim_token === String(params[4]),
|
|
622
|
+
);
|
|
623
|
+
if (idx < 0) return { changes: 0 };
|
|
624
|
+
state.idem.splice(idx, 1);
|
|
625
|
+
return { changes: 1 };
|
|
626
|
+
}
|
|
627
|
+
if (text === IDEM_RENEW_SQL) {
|
|
628
|
+
const row = state.idem.find(
|
|
629
|
+
(candidate) =>
|
|
630
|
+
idemPk(candidate, params, 1) &&
|
|
631
|
+
candidate.claim_token === String(params[5]) &&
|
|
632
|
+
candidate.status === "in_progress",
|
|
633
|
+
);
|
|
634
|
+
if (row === undefined) return { changes: 0 };
|
|
635
|
+
row.lease_expires_at = Number(params[0]);
|
|
636
|
+
return { changes: 1 };
|
|
637
|
+
}
|
|
638
|
+
if (text === IDEM_ATTACH_SQL) {
|
|
639
|
+
const row = state.idem.find(
|
|
640
|
+
(candidate) =>
|
|
641
|
+
idemPk(candidate, params, 1) && candidate.claim_token === String(params[5]),
|
|
642
|
+
);
|
|
643
|
+
if (row === undefined) return { changes: 0 };
|
|
644
|
+
row.run_id = String(params[0]);
|
|
645
|
+
return { changes: 1 };
|
|
646
|
+
}
|
|
647
|
+
if (/^INSERT\s+INTO\s+oke_decision_labels\b/i.test(text)) {
|
|
648
|
+
state.labels.push({
|
|
649
|
+
decision_id: String(params[0]),
|
|
650
|
+
question: String(params[1]),
|
|
651
|
+
value: String(params[2]),
|
|
652
|
+
propensity: Number(params[3]),
|
|
653
|
+
reviewer: String(params[4]),
|
|
654
|
+
locale: params[5] === null || params[5] === undefined ? null : String(params[5]),
|
|
655
|
+
model: params[6] === null || params[6] === undefined ? null : String(params[6]),
|
|
656
|
+
tenant: params[7] === null || params[7] === undefined ? null : String(params[7]),
|
|
657
|
+
score: params[8] === null || params[8] === undefined ? null : Number(params[8]),
|
|
658
|
+
loss: params[9] === null || params[9] === undefined ? null : Number(params[9]),
|
|
659
|
+
raw: params[10] === null || params[10] === undefined ? null : String(params[10]),
|
|
660
|
+
at: Number(params[11]),
|
|
661
|
+
input: params[12] === null || params[12] === undefined ? null : String(params[12]),
|
|
662
|
+
review_id: params[13] === null || params[13] === undefined ? null : String(params[13]),
|
|
663
|
+
});
|
|
664
|
+
return { changes: 1 };
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (/^INSERT\s+INTO\s+oke_decision_candidates\b/i.test(text)) {
|
|
668
|
+
const decisionId = String(params[0]);
|
|
669
|
+
const body = String(params[1]);
|
|
670
|
+
const idx = state.candidates.findIndex((row) => row.decision_id === decisionId);
|
|
671
|
+
if (idx >= 0) state.candidates[idx] = { decision_id: decisionId, body };
|
|
672
|
+
else state.candidates.push({ decision_id: decisionId, body });
|
|
673
|
+
return { changes: 1 };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (/^UPDATE\s+oke_decision_drift\b/i.test(text)) return { changes: 0 };
|
|
677
|
+
|
|
678
|
+
if (/^DELETE\s+FROM\s+oke_decision_drift\b/i.test(text)) {
|
|
679
|
+
const id = String(params[0] ?? "");
|
|
680
|
+
const before = state.drift.length;
|
|
681
|
+
state.drift = state.drift.filter((row) => row.decision_id !== id);
|
|
682
|
+
return { changes: before - state.drift.length };
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (/^INSERT\s+INTO\s+oke_decision_drift\b/i.test(text)) {
|
|
686
|
+
const decisionId = String(params[0]);
|
|
687
|
+
const next = {
|
|
688
|
+
decision_id: decisionId,
|
|
689
|
+
suspended: Number(params[1]),
|
|
690
|
+
certified_at: Number(params[2] ?? 0),
|
|
691
|
+
};
|
|
692
|
+
const idx = state.drift.findIndex((row) => row.decision_id === decisionId);
|
|
693
|
+
if (idx >= 0) state.drift[idx] = next;
|
|
694
|
+
else state.drift.push(next);
|
|
695
|
+
return { changes: 1 };
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (/^UPDATE\s+oke_agent_run\s+SET\s+sweep_claim_at\b/i.test(text)) {
|
|
699
|
+
const until = Number(params[0]);
|
|
700
|
+
const runId = String(params[1] ?? "");
|
|
701
|
+
const now = Number(params[2]);
|
|
702
|
+
const row = state.agentRuns.find((candidate) => candidate.run_id === runId);
|
|
703
|
+
if (!row) return { changes: 0 };
|
|
704
|
+
if (row.sweep_claim_at !== null && row.sweep_claim_at > now) return { changes: 0 };
|
|
705
|
+
row.sweep_claim_at = until;
|
|
706
|
+
return { changes: 1 };
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (/^INSERT\s+INTO\s+oke_agent_run\b/i.test(text)) {
|
|
710
|
+
const runId = String(params[0]);
|
|
711
|
+
const existing = state.agentRuns.find((row) => row.run_id === runId);
|
|
712
|
+
const next = {
|
|
713
|
+
run_id: runId,
|
|
714
|
+
header: String(params[1]),
|
|
715
|
+
sweep_claim_at: existing?.sweep_claim_at ?? null,
|
|
716
|
+
};
|
|
717
|
+
const idx = state.agentRuns.findIndex((row) => row.run_id === runId);
|
|
718
|
+
if (idx >= 0) state.agentRuns[idx] = next;
|
|
719
|
+
else state.agentRuns.push(next);
|
|
720
|
+
return { changes: 1 };
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (/^INSERT\s+INTO\s+oke_agent_event\b/i.test(text)) {
|
|
724
|
+
const runId = String(params[0]);
|
|
725
|
+
const seq = Number(params[1]);
|
|
726
|
+
if (state.agentEvents.some((row) => row.run_id === runId && row.seq === seq)) {
|
|
727
|
+
throw new Error(`duplicate key value violates unique constraint "oke_agent_event_pkey"`);
|
|
728
|
+
}
|
|
729
|
+
state.agentEvents.push({
|
|
730
|
+
run_id: runId,
|
|
731
|
+
seq,
|
|
732
|
+
event: String(params[2]),
|
|
733
|
+
event_type: params[3] === undefined || params[3] === null ? null : String(params[3]),
|
|
734
|
+
event_name: params[4] === undefined || params[4] === null ? null : String(params[4]),
|
|
735
|
+
});
|
|
736
|
+
return { changes: 1 };
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (/^DELETE\s+FROM\s+oke_agent_event\b/i.test(text)) {
|
|
740
|
+
const id = String(params[0] ?? "");
|
|
741
|
+
const before = state.agentEvents.length;
|
|
742
|
+
state.agentEvents = state.agentEvents.filter((row) => row.run_id !== id);
|
|
743
|
+
return { changes: before - state.agentEvents.length };
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (/^DELETE\s+FROM\s+oke_agent_run\b/i.test(text)) {
|
|
747
|
+
const id = String(params[0] ?? "");
|
|
748
|
+
const before = state.agentRuns.length;
|
|
749
|
+
state.agentRuns = state.agentRuns.filter((row) => row.run_id !== id);
|
|
750
|
+
return { changes: before - state.agentRuns.length };
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (text === IDEM_FORFEIT_SQL) {
|
|
754
|
+
const row = state.idem.find(
|
|
755
|
+
(candidate) =>
|
|
756
|
+
idemPk(candidate, params, 1) &&
|
|
757
|
+
candidate.claim_token === String(params[5]) &&
|
|
758
|
+
candidate.status === "in_progress",
|
|
759
|
+
);
|
|
760
|
+
if (row === undefined) return { changes: 0 };
|
|
761
|
+
row.claim_token = String(params[0]);
|
|
762
|
+
row.lease_expires_at = 0;
|
|
763
|
+
return { changes: 1 };
|
|
764
|
+
}
|
|
765
|
+
|
|
391
766
|
throw new Error(`postgres journal fake: unsupported exec: ${sql}`);
|
|
392
767
|
},
|
|
393
768
|
async begin(fn) {
|
|
@@ -441,6 +816,61 @@ export type PostgresJournalStore = JournalStore &
|
|
|
441
816
|
close(): Promise<void>;
|
|
442
817
|
};
|
|
443
818
|
|
|
819
|
+
/**
|
|
820
|
+
* Open decision tables on first use so a journal without decisions stays empty.
|
|
821
|
+
*
|
|
822
|
+
* @param sql - Journal SQL client
|
|
823
|
+
*/
|
|
824
|
+
function loadDecisionLabelModule(): typeof import("../kernel/decision-label-store.ts") {
|
|
825
|
+
return lazyRequire(`${import.meta.dir}/../kernel`, ["decision", "label", "store"].join("-"));
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function loadAgentEventModule(): typeof import("../kernel/agent-event-store.ts") {
|
|
829
|
+
return lazyRequire(`${import.meta.dir}/../kernel`, ["agent", "event", "store"].join("-"));
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function lazyDecisionLabels(sql: PostgresJournalSql): DecisionLabelStore {
|
|
833
|
+
let pending: Promise<DecisionLabelStore> | undefined;
|
|
834
|
+
const ready = (): Promise<DecisionLabelStore> => {
|
|
835
|
+
pending ??= loadDecisionLabelModule().createPostgresDecisionLabelStore(sql);
|
|
836
|
+
return pending;
|
|
837
|
+
};
|
|
838
|
+
return {
|
|
839
|
+
insert: (label, at) => ready().then((store) => store.insert(label, at)),
|
|
840
|
+
list: (decision, tenant) => ready().then((store) => store.list(decision, tenant)),
|
|
841
|
+
drift: () => ready().then((store) => store.drift()),
|
|
842
|
+
setDrift: (decision, record) => ready().then((store) => store.setDrift(decision, record)),
|
|
843
|
+
putCandidate: (decision, entry) => ready().then((store) => store.putCandidate(decision, entry)),
|
|
844
|
+
getCandidate: (decision) => ready().then((store) => store.getCandidate(decision)),
|
|
845
|
+
listCandidates: () => ready().then((store) => store.listCandidates()),
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Open the agent event tables on first use.
|
|
851
|
+
*
|
|
852
|
+
* @param sql - Journal SQL client
|
|
853
|
+
*/
|
|
854
|
+
function lazyAgentEvents(sql: PostgresJournalSql): AgentEventStore {
|
|
855
|
+
let pending: Promise<AgentEventStore> | undefined;
|
|
856
|
+
const ready = (): Promise<AgentEventStore> => {
|
|
857
|
+
pending ??= loadAgentEventModule().createPostgresAgentEventStore(sql);
|
|
858
|
+
return pending;
|
|
859
|
+
};
|
|
860
|
+
return {
|
|
861
|
+
read: (runId) => ready().then((store) => store.read(runId)),
|
|
862
|
+
readAfter: (runId, afterSeq) => ready().then((store) => store.readAfter(runId, afterSeq)),
|
|
863
|
+
maxSeq: (runId) => ready().then((store) => store.maxSeq(runId)),
|
|
864
|
+
readHeader: (runId) => ready().then((store) => store.readHeader(runId)),
|
|
865
|
+
truncated: (runId) => ready().then((store) => store.truncated(runId)),
|
|
866
|
+
claim: (runId) => ready().then((store) => store.claim(runId)),
|
|
867
|
+
listHeaders: () => ready().then((store) => store.listHeaders()),
|
|
868
|
+
writeHeader: (header) => ready().then((store) => store.writeHeader(header)),
|
|
869
|
+
append: (runId, row) => ready().then((store) => store.append(runId, row)),
|
|
870
|
+
remove: (runId) => ready().then((store) => store.remove(runId)),
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
444
874
|
/**
|
|
445
875
|
* Open a postgres-backed JournalStore (multi-host durable-run coordination).
|
|
446
876
|
*
|
|
@@ -460,6 +890,9 @@ export async function createPostgresJournalStore(
|
|
|
460
890
|
|
|
461
891
|
const store: PostgresJournalStore = {
|
|
462
892
|
sql,
|
|
893
|
+
idempotency: createPostgresIdempotencyStore(sql),
|
|
894
|
+
decisions: lazyDecisionLabels(sql),
|
|
895
|
+
agentEvents: lazyAgentEvents(sql),
|
|
463
896
|
async get(runId) {
|
|
464
897
|
const rows = await sql.query(`SELECT * FROM oke_journal_runs WHERE id = ?`, [runId]);
|
|
465
898
|
if (!rows[0]) return undefined;
|
|
@@ -480,6 +913,25 @@ export async function createPostgresJournalStore(
|
|
|
480
913
|
return true;
|
|
481
914
|
});
|
|
482
915
|
},
|
|
916
|
+
async cas(runId, instanceId, now, leaseMs, update) {
|
|
917
|
+
return sql.begin(async (tx) => {
|
|
918
|
+
const claimed = await tx.query(CLAIM_LEASE_SQL, [runId, instanceId, now]);
|
|
919
|
+
const row = claimed[0];
|
|
920
|
+
if (!row) {
|
|
921
|
+
const exists = await tx.query(`SELECT * FROM oke_journal_runs WHERE id = ?`, [runId]);
|
|
922
|
+
const held = exists[0];
|
|
923
|
+
if (!held) return "missing";
|
|
924
|
+
const current = rowToRun(held);
|
|
925
|
+
return { lease: true, leaseExpiresAt: current.leaseExpiresAt };
|
|
926
|
+
}
|
|
927
|
+
const current = rowToRun(row);
|
|
928
|
+
const next = update(current) ?? current;
|
|
929
|
+
next.lockedBy = instanceId;
|
|
930
|
+
next.leaseExpiresAt = now + leaseMs;
|
|
931
|
+
await tx.exec(UPSERT_SQL, runToParams(next));
|
|
932
|
+
return "ok";
|
|
933
|
+
});
|
|
934
|
+
},
|
|
483
935
|
async releaseLease(runId, instanceId) {
|
|
484
936
|
await sql.exec(RELEASE_LEASE_SQL, [runId, instanceId]);
|
|
485
937
|
},
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
TextIndexStore,
|
|
23
23
|
} from "./types.ts";
|
|
24
24
|
import { hostFromUrl } from "./external.ts";
|
|
25
|
+
import { requestSignal } from "../kernel/abort-scope.ts";
|
|
25
26
|
|
|
26
27
|
/** Error thrown when the remote Meilisearch is unreachable / unhealthy / errors. */
|
|
27
28
|
export class MeilisearchUnavailableError extends Error {
|
|
@@ -134,6 +135,7 @@ export async function openMeilisearchIndex(
|
|
|
134
135
|
res = await fetchFn(`${url}${path}`, {
|
|
135
136
|
...init,
|
|
136
137
|
headers: { ...headers, ...(init?.headers ?? {}) },
|
|
138
|
+
signal: requestSignal(init?.signal ?? null),
|
|
137
139
|
});
|
|
138
140
|
} catch (err) {
|
|
139
141
|
throw new MeilisearchUnavailableError(
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory SQL accepts the DDL identity boot issues on the shared connection.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { ensureIdentityTables } from "../auth/identity-sql.ts";
|
|
7
|
+
import { memorySqlDriver } from "./memory.ts";
|
|
8
|
+
|
|
9
|
+
describe("memory sql DDL", () => {
|
|
10
|
+
test("CREATE INDEX IF NOT EXISTS does not reject identity tables", async () => {
|
|
11
|
+
const conn = await memorySqlDriver.connect({ role: "primary" });
|
|
12
|
+
await ensureIdentityTables(conn);
|
|
13
|
+
await ensureIdentityTables(conn);
|
|
14
|
+
const rows = await conn.query("SELECT id FROM oke_credentials");
|
|
15
|
+
expect(rows).toEqual([]);
|
|
16
|
+
await conn.close();
|
|
17
|
+
});
|
|
18
|
+
});
|