@retinue/agentkit 0.1.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/LICENSE +22 -0
- package/README.md +310 -0
- package/dist/adapters/bullmq/consumer.d.ts +33 -0
- package/dist/adapters/bullmq/consumer.js +41 -0
- package/dist/adapters/bullmq/dispatcher.d.ts +74 -0
- package/dist/adapters/bullmq/dispatcher.js +160 -0
- package/dist/adapters/bullmq/export.d.ts +31 -0
- package/dist/adapters/bullmq/export.js +53 -0
- package/dist/adapters/bullmq/extraction.d.ts +42 -0
- package/dist/adapters/bullmq/extraction.js +63 -0
- package/dist/adapters/bullmq/index.d.ts +13 -0
- package/dist/adapters/bullmq/index.js +13 -0
- package/dist/adapters/bullmq/lock.d.ts +77 -0
- package/dist/adapters/bullmq/lock.js +126 -0
- package/dist/adapters/bullmq/queue.d.ts +50 -0
- package/dist/adapters/bullmq/queue.js +81 -0
- package/dist/adapters/memory/artifact-exports.d.ts +11 -0
- package/dist/adapters/memory/artifact-exports.js +102 -0
- package/dist/adapters/memory/artifacts.d.ts +15 -0
- package/dist/adapters/memory/artifacts.js +134 -0
- package/dist/adapters/memory/blobs.d.ts +7 -0
- package/dist/adapters/memory/blobs.js +27 -0
- package/dist/adapters/memory/evaluation.d.ts +18 -0
- package/dist/adapters/memory/evaluation.js +148 -0
- package/dist/adapters/memory/files.d.ts +27 -0
- package/dist/adapters/memory/files.js +0 -0
- package/dist/adapters/memory/flows.d.ts +16 -0
- package/dist/adapters/memory/flows.js +117 -0
- package/dist/adapters/memory/hitl.d.ts +9 -0
- package/dist/adapters/memory/hitl.js +130 -0
- package/dist/adapters/memory/idempotency.d.ts +13 -0
- package/dist/adapters/memory/idempotency.js +32 -0
- package/dist/adapters/memory/index.d.ts +39 -0
- package/dist/adapters/memory/index.js +107 -0
- package/dist/adapters/memory/knowledge.d.ts +43 -0
- package/dist/adapters/memory/knowledge.js +248 -0
- package/dist/adapters/memory/mcp.d.ts +9 -0
- package/dist/adapters/memory/mcp.js +37 -0
- package/dist/adapters/memory/message-store.d.ts +17 -0
- package/dist/adapters/memory/message-store.js +70 -0
- package/dist/adapters/memory/principal-memory.d.ts +7 -0
- package/dist/adapters/memory/principal-memory.js +83 -0
- package/dist/adapters/memory/runtime.d.ts +29 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/memory/sessions.d.ts +29 -0
- package/dist/adapters/memory/sessions.js +0 -0
- package/dist/adapters/memory/skills.d.ts +10 -0
- package/dist/adapters/memory/skills.js +41 -0
- package/dist/adapters/memory/thread-summary.d.ts +7 -0
- package/dist/adapters/memory/thread-summary.js +29 -0
- package/dist/adapters/memory/usage-limits.d.ts +13 -0
- package/dist/adapters/memory/usage-limits.js +72 -0
- package/dist/adapters/memory/usage.d.ts +16 -0
- package/dist/adapters/memory/usage.js +279 -0
- package/dist/adapters/otel/index.d.ts +111 -0
- package/dist/adapters/otel/index.js +133 -0
- package/dist/adapters/postgres/artifact-exports.d.ts +12 -0
- package/dist/adapters/postgres/artifact-exports.js +117 -0
- package/dist/adapters/postgres/artifacts.d.ts +16 -0
- package/dist/adapters/postgres/artifacts.js +172 -0
- package/dist/adapters/postgres/checkpoint-store.d.ts +16 -0
- package/dist/adapters/postgres/checkpoint-store.js +34 -0
- package/dist/adapters/postgres/config.d.ts +15 -0
- package/dist/adapters/postgres/config.js +187 -0
- package/dist/adapters/postgres/conversation-store.d.ts +4 -0
- package/dist/adapters/postgres/conversation-store.js +82 -0
- package/dist/adapters/postgres/evaluation.d.ts +17 -0
- package/dist/adapters/postgres/evaluation.js +193 -0
- package/dist/adapters/postgres/file-content.d.ts +30 -0
- package/dist/adapters/postgres/file-content.js +111 -0
- package/dist/adapters/postgres/files.d.ts +19 -0
- package/dist/adapters/postgres/files.js +209 -0
- package/dist/adapters/postgres/flows.d.ts +20 -0
- package/dist/adapters/postgres/flows.js +206 -0
- package/dist/adapters/postgres/hitl.d.ts +5 -0
- package/dist/adapters/postgres/hitl.js +247 -0
- package/dist/adapters/postgres/index.d.ts +35 -0
- package/dist/adapters/postgres/index.js +35 -0
- package/dist/adapters/postgres/knowledge.d.ts +48 -0
- package/dist/adapters/postgres/knowledge.js +255 -0
- package/dist/adapters/postgres/memory.d.ts +14 -0
- package/dist/adapters/postgres/memory.js +194 -0
- package/dist/adapters/postgres/message-store.d.ts +11 -0
- package/dist/adapters/postgres/message-store.js +145 -0
- package/dist/adapters/postgres/migrations.d.ts +69 -0
- package/dist/adapters/postgres/migrations.js +1594 -0
- package/dist/adapters/postgres/pg-executor.d.ts +19 -0
- package/dist/adapters/postgres/pg-executor.js +32 -0
- package/dist/adapters/postgres/retention.d.ts +26 -0
- package/dist/adapters/postgres/retention.js +59 -0
- package/dist/adapters/postgres/rollups.d.ts +17 -0
- package/dist/adapters/postgres/rollups.js +267 -0
- package/dist/adapters/postgres/run-coordinator.d.ts +5 -0
- package/dist/adapters/postgres/run-coordinator.js +98 -0
- package/dist/adapters/postgres/run-event-log.d.ts +26 -0
- package/dist/adapters/postgres/run-event-log.js +30 -0
- package/dist/adapters/postgres/run-store.d.ts +4 -0
- package/dist/adapters/postgres/run-store.js +199 -0
- package/dist/adapters/postgres/schema.d.ts +39 -0
- package/dist/adapters/postgres/schema.js +70 -0
- package/dist/adapters/postgres/session-state.d.ts +7 -0
- package/dist/adapters/postgres/session-state.js +99 -0
- package/dist/adapters/postgres/sql.d.ts +8 -0
- package/dist/adapters/postgres/sql.js +2 -0
- package/dist/adapters/postgres/transaction.d.ts +37 -0
- package/dist/adapters/postgres/transaction.js +93 -0
- package/dist/adapters/postgres/unit-of-work.d.ts +18 -0
- package/dist/adapters/postgres/unit-of-work.js +8 -0
- package/dist/adapters/postgres/usage-limits.d.ts +15 -0
- package/dist/adapters/postgres/usage-limits.js +136 -0
- package/dist/adapters/postgres/usage.d.ts +15 -0
- package/dist/adapters/postgres/usage.js +226 -0
- package/dist/adapters/redis/index.d.ts +9 -0
- package/dist/adapters/redis/index.js +9 -0
- package/dist/adapters/redis/realtime.d.ts +74 -0
- package/dist/adapters/redis/realtime.js +112 -0
- package/dist/adapters/supabase/index.d.ts +88 -0
- package/dist/adapters/supabase/index.js +84 -0
- package/dist/adapters/supabase/realtime.d.ts +30 -0
- package/dist/adapters/supabase/realtime.js +53 -0
- package/dist/adapters/supabase/rls.d.ts +99 -0
- package/dist/adapters/supabase/rls.js +216 -0
- package/dist/adapters/supabase/storage.d.ts +50 -0
- package/dist/adapters/supabase/storage.js +207 -0
- package/dist/agents/agent.d.ts +66 -0
- package/dist/agents/agent.js +209 -0
- package/dist/agents/define.d.ts +21 -0
- package/dist/agents/define.js +63 -0
- package/dist/agents/engine.d.ts +98 -0
- package/dist/agents/engine.js +462 -0
- package/dist/agents/index.d.ts +50 -0
- package/dist/agents/index.js +17 -0
- package/dist/artifacts/index.d.ts +114 -0
- package/dist/artifacts/index.js +219 -0
- package/dist/authorization/index.d.ts +76 -0
- package/dist/authorization/index.js +76 -0
- package/dist/capabilities/index.d.ts +120 -0
- package/dist/capabilities/index.js +167 -0
- package/dist/capabilities/runtime.d.ts +89 -0
- package/dist/capabilities/runtime.js +84 -0
- package/dist/citations/index.d.ts +161 -0
- package/dist/citations/index.js +182 -0
- package/dist/context/assembler.d.ts +82 -0
- package/dist/context/assembler.js +129 -0
- package/dist/context/compaction.d.ts +45 -0
- package/dist/context/compaction.js +55 -0
- package/dist/context/index.d.ts +75 -0
- package/dist/context/index.js +17 -0
- package/dist/core/content-parts.d.ts +194 -0
- package/dist/core/content-parts.js +23 -0
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.js +9 -0
- package/dist/core/env.d.ts +25 -0
- package/dist/core/env.js +41 -0
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/errors.js +65 -0
- package/dist/core/events.d.ts +139 -0
- package/dist/core/events.js +99 -0
- package/dist/core/ids.d.ts +52 -0
- package/dist/core/ids.js +13 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/tokens.d.ts +22 -0
- package/dist/core/tokens.js +22 -0
- package/dist/core/validation.d.ts +34 -0
- package/dist/core/validation.js +176 -0
- package/dist/documents/extraction.d.ts +121 -0
- package/dist/documents/extraction.js +293 -0
- package/dist/documents/index.d.ts +199 -0
- package/dist/documents/index.js +65 -0
- package/dist/documents/parsers/pdf.d.ts +47 -0
- package/dist/documents/parsers/pdf.js +508 -0
- package/dist/documents/parsers/text.d.ts +59 -0
- package/dist/documents/parsers/text.js +325 -0
- package/dist/documents/read-tool.d.ts +52 -0
- package/dist/documents/read-tool.js +109 -0
- package/dist/documents/render.d.ts +29 -0
- package/dist/documents/render.js +59 -0
- package/dist/documents/vision.d.ts +159 -0
- package/dist/documents/vision.js +214 -0
- package/dist/entries/adapters-bullmq.d.ts +8 -0
- package/dist/entries/adapters-bullmq.js +8 -0
- package/dist/entries/adapters-otel.d.ts +13 -0
- package/dist/entries/adapters-otel.js +13 -0
- package/dist/entries/adapters-postgres.d.ts +10 -0
- package/dist/entries/adapters-postgres.js +10 -0
- package/dist/entries/adapters-redis.d.ts +3 -0
- package/dist/entries/adapters-redis.js +3 -0
- package/dist/entries/context.d.ts +20 -0
- package/dist/entries/context.js +20 -0
- package/dist/entries/flows.d.ts +15 -0
- package/dist/entries/flows.js +15 -0
- package/dist/entries/hitl.d.ts +10 -0
- package/dist/entries/hitl.js +10 -0
- package/dist/entries/knowledge.d.ts +18 -0
- package/dist/entries/knowledge.js +19 -0
- package/dist/entries/mcp.d.ts +10 -0
- package/dist/entries/mcp.js +10 -0
- package/dist/entries/observability.d.ts +14 -0
- package/dist/entries/observability.js +16 -0
- package/dist/entries/persistence.d.ts +11 -0
- package/dist/entries/persistence.js +11 -0
- package/dist/entries/providers.d.ts +14 -0
- package/dist/entries/providers.js +14 -0
- package/dist/entries/runtime.d.ts +13 -0
- package/dist/entries/runtime.js +13 -0
- package/dist/entries/server.d.ts +24 -0
- package/dist/entries/server.js +24 -0
- package/dist/entries/tools.d.ts +21 -0
- package/dist/entries/tools.js +21 -0
- package/dist/entries/usage.d.ts +10 -0
- package/dist/entries/usage.js +10 -0
- package/dist/evaluation/gate.d.ts +168 -0
- package/dist/evaluation/gate.js +180 -0
- package/dist/evaluation/graders.d.ts +125 -0
- package/dist/evaluation/graders.js +203 -0
- package/dist/evaluation/index.d.ts +120 -0
- package/dist/evaluation/index.js +183 -0
- package/dist/evaluation/judge.d.ts +75 -0
- package/dist/evaluation/judge.js +111 -0
- package/dist/export/index.d.ts +162 -0
- package/dist/export/index.js +363 -0
- package/dist/export/markdown.d.ts +19 -0
- package/dist/export/markdown.js +29 -0
- package/dist/export/pdf.d.ts +73 -0
- package/dist/export/pdf.js +407 -0
- package/dist/files/context.d.ts +97 -0
- package/dist/files/context.js +185 -0
- package/dist/files/index.d.ts +210 -0
- package/dist/files/index.js +338 -0
- package/dist/files/read-tool.d.ts +81 -0
- package/dist/files/read-tool.js +163 -0
- package/dist/files/turn-parts.d.ts +96 -0
- package/dist/files/turn-parts.js +171 -0
- package/dist/flows/index.d.ts +270 -0
- package/dist/flows/index.js +62 -0
- package/dist/flows/interpreter.d.ts +146 -0
- package/dist/flows/interpreter.js +426 -0
- package/dist/flows/runner.d.ts +145 -0
- package/dist/flows/runner.js +270 -0
- package/dist/graphql/index.d.ts +8 -0
- package/dist/graphql/index.js +8 -0
- package/dist/graphql/resolvers.d.ts +237 -0
- package/dist/graphql/resolvers.js +253 -0
- package/dist/graphql/schema.d.ts +11 -0
- package/dist/graphql/schema.js +258 -0
- package/dist/graphql/sse.d.ts +77 -0
- package/dist/graphql/sse.js +100 -0
- package/dist/hitl/approved-execution.d.ts +127 -0
- package/dist/hitl/approved-execution.js +177 -0
- package/dist/hitl/index.d.ts +79 -0
- package/dist/hitl/index.js +12 -0
- package/dist/hitl/service.d.ts +221 -0
- package/dist/hitl/service.js +268 -0
- package/dist/idempotency/index.d.ts +70 -0
- package/dist/idempotency/index.js +59 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +60 -0
- package/dist/knowledge/chunking.d.ts +57 -0
- package/dist/knowledge/chunking.js +158 -0
- package/dist/knowledge/index.d.ts +119 -0
- package/dist/knowledge/index.js +166 -0
- package/dist/knowledge/retrieval.d.ts +146 -0
- package/dist/knowledge/retrieval.js +170 -0
- package/dist/loadtest/harness.d.ts +168 -0
- package/dist/loadtest/harness.js +507 -0
- package/dist/loadtest/index.d.ts +13 -0
- package/dist/loadtest/index.js +13 -0
- package/dist/loadtest/injection.d.ts +89 -0
- package/dist/loadtest/injection.js +147 -0
- package/dist/loadtest/metrics.d.ts +197 -0
- package/dist/loadtest/metrics.js +160 -0
- package/dist/loadtest/runbooks.d.ts +28 -0
- package/dist/loadtest/runbooks.js +159 -0
- package/dist/loadtest/scenario.d.ts +104 -0
- package/dist/loadtest/scenario.js +208 -0
- package/dist/mcp/egress.d.ts +53 -0
- package/dist/mcp/egress.js +115 -0
- package/dist/mcp/index.d.ts +93 -0
- package/dist/mcp/index.js +33 -0
- package/dist/mcp/provider.d.ts +62 -0
- package/dist/mcp/provider.js +0 -0
- package/dist/models/index.d.ts +98 -0
- package/dist/models/index.js +74 -0
- package/dist/models/pricing.d.ts +24 -0
- package/dist/models/pricing.js +37 -0
- package/dist/models/provider-factory.d.ts +31 -0
- package/dist/models/provider-factory.js +67 -0
- package/dist/models/streaming.d.ts +145 -0
- package/dist/models/streaming.js +272 -0
- package/dist/models/vision.d.ts +38 -0
- package/dist/models/vision.js +62 -0
- package/dist/persistence/index.d.ts +1654 -0
- package/dist/persistence/index.js +226 -0
- package/dist/principal-memory/index.d.ts +106 -0
- package/dist/principal-memory/index.js +89 -0
- package/dist/retention/index.d.ts +89 -0
- package/dist/retention/index.js +70 -0
- package/dist/runtime/checkpoint.d.ts +37 -0
- package/dist/runtime/checkpoint.js +22 -0
- package/dist/runtime/index.d.ts +118 -0
- package/dist/runtime/index.js +69 -0
- package/dist/runtime/retry.d.ts +95 -0
- package/dist/runtime/retry.js +126 -0
- package/dist/runtime/serialization.d.ts +85 -0
- package/dist/runtime/serialization.js +95 -0
- package/dist/runtime/streaming.d.ts +54 -0
- package/dist/runtime/streaming.js +115 -0
- package/dist/runtime/worker.d.ts +130 -0
- package/dist/runtime/worker.js +405 -0
- package/dist/security/checklist.d.ts +53 -0
- package/dist/security/checklist.js +204 -0
- package/dist/security/findings.d.ts +56 -0
- package/dist/security/findings.js +168 -0
- package/dist/security/index.d.ts +14 -0
- package/dist/security/index.js +14 -0
- package/dist/security/prompt-safety.d.ts +100 -0
- package/dist/security/prompt-safety.js +133 -0
- package/dist/server/boot.d.ts +32 -0
- package/dist/server/boot.js +36 -0
- package/dist/server/cli-worker.d.ts +37 -0
- package/dist/server/cli-worker.js +151 -0
- package/dist/server/cli.d.ts +27 -0
- package/dist/server/cli.js +74 -0
- package/dist/server/config.d.ts +42 -0
- package/dist/server/config.js +127 -0
- package/dist/server/health.d.ts +59 -0
- package/dist/server/health.js +90 -0
- package/dist/server/host.d.ts +39 -0
- package/dist/server/host.js +124 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +15 -0
- package/dist/server/main.d.ts +16 -0
- package/dist/server/main.js +31 -0
- package/dist/server/sse-route.d.ts +21 -0
- package/dist/server/sse-route.js +282 -0
- package/dist/skills/index.d.ts +67 -0
- package/dist/skills/index.js +31 -0
- package/dist/skills/resolver.d.ts +54 -0
- package/dist/skills/resolver.js +121 -0
- package/dist/teams/index.d.ts +93 -0
- package/dist/teams/index.js +207 -0
- package/dist/telemetry/index.d.ts +157 -0
- package/dist/telemetry/index.js +71 -0
- package/dist/telemetry/instrument.d.ts +108 -0
- package/dist/telemetry/instrument.js +232 -0
- package/dist/telemetry/log-events.d.ts +17 -0
- package/dist/telemetry/log-events.js +58 -0
- package/dist/telemetry/metrics.d.ts +123 -0
- package/dist/telemetry/metrics.js +135 -0
- package/dist/telemetry/noop.d.ts +39 -0
- package/dist/telemetry/noop.js +143 -0
- package/dist/telemetry/redaction.d.ts +64 -0
- package/dist/telemetry/redaction.js +153 -0
- package/dist/telemetry/spans.d.ts +56 -0
- package/dist/telemetry/spans.js +78 -0
- package/dist/telemetry/trace-context.d.ts +55 -0
- package/dist/telemetry/trace-context.js +60 -0
- package/dist/toolkit/compute.d.ts +53 -0
- package/dist/toolkit/compute.js +152 -0
- package/dist/toolkit/data.d.ts +98 -0
- package/dist/toolkit/data.js +235 -0
- package/dist/toolkit/http.d.ts +113 -0
- package/dist/toolkit/http.js +205 -0
- package/dist/toolkit/index.d.ts +21 -0
- package/dist/toolkit/index.js +17 -0
- package/dist/toolkit/web.d.ts +107 -0
- package/dist/toolkit/web.js +147 -0
- package/dist/tools/define.d.ts +25 -0
- package/dist/tools/define.js +45 -0
- package/dist/tools/delegating.d.ts +132 -0
- package/dist/tools/delegating.js +211 -0
- package/dist/tools/index.d.ts +129 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/library/compute.d.ts +11 -0
- package/dist/tools/library/compute.js +46 -0
- package/dist/tools/library/data.d.ts +16 -0
- package/dist/tools/library/data.js +92 -0
- package/dist/tools/library/http.d.ts +28 -0
- package/dist/tools/library/http.js +71 -0
- package/dist/tools/library/index.d.ts +97 -0
- package/dist/tools/library/index.js +134 -0
- package/dist/tools/library/knowledge.d.ts +39 -0
- package/dist/tools/library/knowledge.js +58 -0
- package/dist/tools/library/web.d.ts +19 -0
- package/dist/tools/library/web.js +65 -0
- package/dist/tools/meta-tools.d.ts +19 -0
- package/dist/tools/meta-tools.js +36 -0
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +291 -0
- package/dist/usage/index.d.ts +105 -0
- package/dist/usage/index.js +20 -0
- package/dist/usage/quota.d.ts +258 -0
- package/dist/usage/quota.js +510 -0
- package/dist/usage/recorder.d.ts +29 -0
- package/dist/usage/recorder.js +96 -0
- package/dist/usage/rollups.d.ts +121 -0
- package/dist/usage/rollups.js +157 -0
- package/dist/worker/export.d.ts +57 -0
- package/dist/worker/export.js +81 -0
- package/dist/worker/extraction.d.ts +57 -0
- package/dist/worker/extraction.js +84 -0
- package/dist/worker/main.d.ts +103 -0
- package/dist/worker/main.js +159 -0
- package/package.json +187 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL `RunStore` (#93). Pure SQL over a `SqlExecutor`; verified by `runStoreConformance`,
|
|
3
|
+
* the same suite the in-memory adapter passes.
|
|
4
|
+
*
|
|
5
|
+
* Every state change is a **single compare-and-set `UPDATE … RETURNING`**, never read-then-write.
|
|
6
|
+
* That is the whole point of this adapter: the in-memory store's "two workers cannot claim one run"
|
|
7
|
+
* guarantee comes from JavaScript being single-threaded, which says nothing about two worker
|
|
8
|
+
* processes. Here the database adjudicates, so the guarantee survives real deployment.
|
|
9
|
+
*
|
|
10
|
+
* Where a caller needs to distinguish *why* no row changed (missing vs. illegal vs. held by someone
|
|
11
|
+
* else), the follow-up read happens only on the failure path — so the happy path stays one
|
|
12
|
+
* statement.
|
|
13
|
+
*/
|
|
14
|
+
import { AgentPlatformError } from "../../core/errors.js";
|
|
15
|
+
import { canTransition, isTerminal } from "../../runtime/index.js";
|
|
16
|
+
const iso = (v) => (v instanceof Date ? v.toISOString() : new Date(v).toISOString());
|
|
17
|
+
const toRun = (r) => ({
|
|
18
|
+
id: r.id,
|
|
19
|
+
tenantId: r.tenant_id,
|
|
20
|
+
// Spread conditionally, so an absent conversation is *absent* from the object rather than an explicit
|
|
21
|
+
// `undefined` — `"conversationId" in run` then answers "does this belong to a conversation" without a
|
|
22
|
+
// second convention, which is how every other optional field in these adapters behaves (#198).
|
|
23
|
+
...(r.conversation_id === null ? {} : { conversationId: r.conversation_id }),
|
|
24
|
+
agentId: r.agent_id,
|
|
25
|
+
agentVersion: r.agent_version,
|
|
26
|
+
// Absent stays absent: a run with no recorded input is different from one whose input was null.
|
|
27
|
+
...(r.input === null || r.input === undefined
|
|
28
|
+
? {}
|
|
29
|
+
: { input: typeof r.input === "string" ? JSON.parse(r.input) : r.input }),
|
|
30
|
+
...(r.limits === null || r.limits === undefined
|
|
31
|
+
? {}
|
|
32
|
+
: { limits: (typeof r.limits === "string" ? JSON.parse(r.limits) : r.limits) }),
|
|
33
|
+
status: r.status,
|
|
34
|
+
createdAt: iso(r.created_at),
|
|
35
|
+
...(r.started_at === null ? {} : { startedAt: iso(r.started_at) }),
|
|
36
|
+
...(r.finished_at === null ? {} : { finishedAt: iso(r.finished_at) }),
|
|
37
|
+
...(r.error === null || r.error === undefined ? {} : { error: r.error }),
|
|
38
|
+
...(r.claimed_by === null ? {} : { claimedBy: r.claimed_by }),
|
|
39
|
+
...(r.keepalive_at === null ? {} : { keepaliveAt: iso(r.keepalive_at) }),
|
|
40
|
+
...(r.lease_expires_at === null ? {} : { leaseExpiresAt: iso(r.lease_expires_at) }),
|
|
41
|
+
...(r.cancel_requested_at === null ? {} : { cancelRequestedAt: iso(r.cancel_requested_at) }),
|
|
42
|
+
// #164. Null-and-undefined both mean "not recorded": the column is nullable for rows written before it
|
|
43
|
+
// existed, and `??` here rather than a `?? []` default because an empty role list and an unknown one are
|
|
44
|
+
// different facts — one is a caller with no roles, the other is a run nobody can attribute.
|
|
45
|
+
...(r.principal_id === null || r.principal_id === undefined ? {} : { principalId: r.principal_id }),
|
|
46
|
+
...(r.role_ids === null || r.role_ids === undefined ? {} : { roleIds: r.role_ids }),
|
|
47
|
+
});
|
|
48
|
+
const conflict = (m) => new AgentPlatformError({ code: "conflict", message: m, retryable: false });
|
|
49
|
+
const notFound = (id) => new AgentPlatformError({ code: "not_found", message: `Run ${id} not found`, retryable: false });
|
|
50
|
+
/** The statuses that can never hold a lease or transition further. Mirrors `isTerminal`. */
|
|
51
|
+
const TERMINAL_SQL = `('completed', 'failed', 'cancelled')`;
|
|
52
|
+
export const createPostgresRunStore = (sql) => {
|
|
53
|
+
const read = async (tenantId, id) => {
|
|
54
|
+
const rows = await sql.query(`SELECT * FROM runs WHERE tenant_id = $1 AND id = $2`, [tenantId, id]);
|
|
55
|
+
const row = rows[0];
|
|
56
|
+
return row ? toRun(row) : null;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
async create({ tenantId, id, conversationId, agentId, agentVersion, principalId, roleIds, input, limits }) {
|
|
60
|
+
const rows = await sql.query(`INSERT INTO runs (tenant_id, id, conversation_id, agent_id, agent_version, status, created_at,
|
|
61
|
+
principal_id, role_ids, input, limits)
|
|
62
|
+
VALUES ($1, $2, $3, $4, $5, 'queued', now(), $6, $7, $8::jsonb, $9::jsonb)
|
|
63
|
+
ON CONFLICT (tenant_id, id) DO NOTHING
|
|
64
|
+
RETURNING *`,
|
|
65
|
+
// `?? null` on the conversation since #198: absent means the run belongs to none, and a
|
|
66
|
+
// fabricated id here would be indistinguishable from a real conversation in every later query.
|
|
67
|
+
// `input` and `limits` since #202: a conversation-less run's request has nowhere else to live, and a
|
|
68
|
+
// flow's remaining budget has to reach the child it admits.
|
|
69
|
+
[
|
|
70
|
+
tenantId,
|
|
71
|
+
id,
|
|
72
|
+
conversationId ?? null,
|
|
73
|
+
agentId,
|
|
74
|
+
agentVersion,
|
|
75
|
+
principalId ?? null,
|
|
76
|
+
roleIds ?? null,
|
|
77
|
+
input === undefined ? null : JSON.stringify(input),
|
|
78
|
+
limits === undefined ? null : JSON.stringify(limits),
|
|
79
|
+
]);
|
|
80
|
+
const row = rows[0];
|
|
81
|
+
if (!row)
|
|
82
|
+
throw conflict(`Run ${id} already exists`);
|
|
83
|
+
return toRun(row);
|
|
84
|
+
},
|
|
85
|
+
async findById({ tenantId, id }) {
|
|
86
|
+
return read(tenantId, id);
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* Lease-based claim, in one statement. Two claimable cases:
|
|
90
|
+
* - `queued` — a cold start.
|
|
91
|
+
* - `running` with an expired lease — **crash recovery**, the case the SPEC's original
|
|
92
|
+
* `WHERE status='queued'` would have removed.
|
|
93
|
+
* A live lease held by *another* worker excludes the row; the current holder re-claiming is
|
|
94
|
+
* allowed (idempotent re-entry after a transient failure).
|
|
95
|
+
*/
|
|
96
|
+
async claim({ tenantId, id, workerId, leaseMs, now }) {
|
|
97
|
+
const rows = await sql.query(`UPDATE runs
|
|
98
|
+
SET status = 'running',
|
|
99
|
+
claimed_by = $3,
|
|
100
|
+
keepalive_at = $4::timestamptz,
|
|
101
|
+
lease_expires_at = $4::timestamptz + ($5 || ' milliseconds')::interval,
|
|
102
|
+
-- Only on the first claim: a recovered run keeps its original start time.
|
|
103
|
+
started_at = COALESCE(started_at, $4::timestamptz)
|
|
104
|
+
WHERE tenant_id = $1
|
|
105
|
+
AND id = $2
|
|
106
|
+
AND status NOT IN ${TERMINAL_SQL}
|
|
107
|
+
AND (status = 'queued' OR (status = 'running' AND lease_expires_at <= $4::timestamptz))
|
|
108
|
+
AND (claimed_by IS NULL
|
|
109
|
+
OR claimed_by = $3
|
|
110
|
+
OR lease_expires_at IS NULL
|
|
111
|
+
OR lease_expires_at <= $4::timestamptz)
|
|
112
|
+
RETURNING *`, [tenantId, id, workerId, now, String(leaseMs)]);
|
|
113
|
+
const row = rows[0];
|
|
114
|
+
return row ? toRun(row) : null;
|
|
115
|
+
},
|
|
116
|
+
/** False when the claim was lost (reaped or stolen) so the worker aborts rather than continuing. */
|
|
117
|
+
async keepalive({ tenantId, id, workerId, leaseMs, now }) {
|
|
118
|
+
const rows = await sql.query(`UPDATE runs
|
|
119
|
+
SET keepalive_at = $4::timestamptz,
|
|
120
|
+
lease_expires_at = $4::timestamptz + ($5 || ' milliseconds')::interval
|
|
121
|
+
WHERE tenant_id = $1
|
|
122
|
+
AND id = $2
|
|
123
|
+
AND claimed_by = $3
|
|
124
|
+
AND status NOT IN ${TERMINAL_SQL}
|
|
125
|
+
RETURNING id`, [tenantId, id, workerId, now, String(leaseMs)]);
|
|
126
|
+
return rows.length > 0;
|
|
127
|
+
},
|
|
128
|
+
/**
|
|
129
|
+
* Guarded transition by the claiming worker. Legality is decided in TypeScript from
|
|
130
|
+
* `RUN_TRANSITIONS` rather than duplicated in SQL — one source of truth, and the error message
|
|
131
|
+
* can name the illegal move. A same-status transition is a no-op rather than an error.
|
|
132
|
+
*
|
|
133
|
+
* Terminal and `waiting-for-*` states release the claim and lease, so a continuation can
|
|
134
|
+
* re-claim the run without waiting for the lease to expire.
|
|
135
|
+
*/
|
|
136
|
+
async transition({ tenantId, id, workerId, to, now, error }) {
|
|
137
|
+
const current = await read(tenantId, id);
|
|
138
|
+
if (!current)
|
|
139
|
+
throw notFound(id);
|
|
140
|
+
if (current.claimedBy !== undefined && current.claimedBy !== workerId)
|
|
141
|
+
throw conflict(`Run ${id} is held by another worker`);
|
|
142
|
+
if (current.status !== to && !canTransition(current.status, to))
|
|
143
|
+
throw conflict(`Illegal run transition ${current.status} -> ${to}`);
|
|
144
|
+
const releases = isTerminal(to) || to === "waiting-for-question" || to === "waiting-for-approval";
|
|
145
|
+
const rows = await sql.query(`UPDATE runs
|
|
146
|
+
SET status = $3,
|
|
147
|
+
error = COALESCE($4::jsonb, error),
|
|
148
|
+
finished_at = CASE WHEN $5 THEN $6::timestamptz ELSE finished_at END,
|
|
149
|
+
claimed_by = CASE WHEN $7 THEN NULL ELSE claimed_by END,
|
|
150
|
+
lease_expires_at = CASE WHEN $7 THEN NULL ELSE lease_expires_at END
|
|
151
|
+
WHERE tenant_id = $1
|
|
152
|
+
AND id = $2
|
|
153
|
+
-- Re-check the status we based the legality decision on, so a concurrent transition
|
|
154
|
+
-- cannot slip between the read and this write.
|
|
155
|
+
AND status = $8
|
|
156
|
+
RETURNING *`, [
|
|
157
|
+
tenantId,
|
|
158
|
+
id,
|
|
159
|
+
to,
|
|
160
|
+
error === undefined ? null : JSON.stringify(error),
|
|
161
|
+
isTerminal(to),
|
|
162
|
+
now,
|
|
163
|
+
releases,
|
|
164
|
+
current.status,
|
|
165
|
+
]);
|
|
166
|
+
const row = rows[0];
|
|
167
|
+
// Lost the race: another worker moved the run between our read and our write.
|
|
168
|
+
if (!row)
|
|
169
|
+
throw conflict(`Run ${id} changed status concurrently; transition to ${to} abandoned`);
|
|
170
|
+
return toRun(row);
|
|
171
|
+
},
|
|
172
|
+
/** Durable cancel request. Idempotent — the first timestamp wins — and a no-op once terminal. */
|
|
173
|
+
async requestCancel({ tenantId, id, now }) {
|
|
174
|
+
const rows = await sql.query(`UPDATE runs
|
|
175
|
+
SET cancel_requested_at = COALESCE(cancel_requested_at, $3::timestamptz)
|
|
176
|
+
WHERE tenant_id = $1 AND id = $2 AND status NOT IN ${TERMINAL_SQL}
|
|
177
|
+
RETURNING *`, [tenantId, id, now]);
|
|
178
|
+
const row = rows[0];
|
|
179
|
+
// No row updated ⇒ either missing or already terminal. The port returns the run as-is for a
|
|
180
|
+
// terminal run and null when it does not exist, so fall back to a read.
|
|
181
|
+
return row ? toRun(row) : await read(tenantId, id);
|
|
182
|
+
},
|
|
183
|
+
/**
|
|
184
|
+
* Recovery candidates: running runs whose lease expired. Cross-tenant by design — a background
|
|
185
|
+
* reaper has no tenant — so each row carries its own `tenantId` for re-claim, and the supporting
|
|
186
|
+
* index is deliberately not tenant-leading.
|
|
187
|
+
*/
|
|
188
|
+
async reapExpired({ now, limit }) {
|
|
189
|
+
const rows = await sql.query(`SELECT * FROM runs
|
|
190
|
+
WHERE status = 'running'
|
|
191
|
+
AND lease_expires_at IS NOT NULL
|
|
192
|
+
AND lease_expires_at <= $1::timestamptz
|
|
193
|
+
ORDER BY lease_expires_at
|
|
194
|
+
LIMIT $2`, [now, limit]);
|
|
195
|
+
return rows.map(toRun);
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=run-store.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic schema provisioning — `docs/02-core-and-persistence.md`.
|
|
3
|
+
*
|
|
4
|
+
* A `SchemaManager` over the reversible migrations: it provisions a fresh database on startup
|
|
5
|
+
* (`auto`), logs the diff and refuses (`plan`), or leaves the schema to managed migrations
|
|
6
|
+
* (`off`, the default for Postgres). Forward-only and idempotent — running it twice is a no-op,
|
|
7
|
+
* so concurrent workers never double-provision.
|
|
8
|
+
*/
|
|
9
|
+
import { type Migration } from "./migrations.js";
|
|
10
|
+
import type { SqlExecutor } from "./sql.js";
|
|
11
|
+
export type SchemaMode = "auto" | "plan" | "off";
|
|
12
|
+
export type SchemaChange = {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly statements: readonly string[];
|
|
15
|
+
};
|
|
16
|
+
export interface SchemaManager {
|
|
17
|
+
currentVersion(): Promise<number>;
|
|
18
|
+
targetVersion(): number;
|
|
19
|
+
/** Pending changes — no side effects. */
|
|
20
|
+
plan(): Promise<readonly SchemaChange[]>;
|
|
21
|
+
/** Create/upgrade to the target version. Idempotent. */
|
|
22
|
+
apply(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare const createSchemaManager: (sql: SqlExecutor, migrations?: readonly Migration[]) => SchemaManager;
|
|
25
|
+
export type ProvisionResult = {
|
|
26
|
+
readonly mode: SchemaMode;
|
|
27
|
+
readonly applied: readonly string[];
|
|
28
|
+
readonly planned: readonly SchemaChange[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Startup provisioning per mode. `off` (Postgres default) leaves the schema to managed
|
|
32
|
+
* migrations; `plan` logs the pending diff and applies nothing; `auto` provisions.
|
|
33
|
+
*/
|
|
34
|
+
export declare const provisionSchema: (sql: SqlExecutor, options?: {
|
|
35
|
+
readonly mode?: SchemaMode;
|
|
36
|
+
readonly log?: (message: string) => void;
|
|
37
|
+
readonly migrations?: readonly Migration[];
|
|
38
|
+
}) => Promise<ProvisionResult>;
|
|
39
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic schema provisioning — `docs/02-core-and-persistence.md`.
|
|
3
|
+
*
|
|
4
|
+
* A `SchemaManager` over the reversible migrations: it provisions a fresh database on startup
|
|
5
|
+
* (`auto`), logs the diff and refuses (`plan`), or leaves the schema to managed migrations
|
|
6
|
+
* (`off`, the default for Postgres). Forward-only and idempotent — running it twice is a no-op,
|
|
7
|
+
* so concurrent workers never double-provision.
|
|
8
|
+
*/
|
|
9
|
+
import { MIGRATION_LEDGER, MIGRATIONS, appliedMigrationIds } from "./migrations.js";
|
|
10
|
+
export const createSchemaManager = (sql, migrations = MIGRATIONS) => {
|
|
11
|
+
// Read-only: if the tracking table doesn't exist yet, nothing has been applied. `plan()` and
|
|
12
|
+
// `currentVersion()` therefore have no side effects; only `apply()` creates the table.
|
|
13
|
+
// Intersected with this manager's own list, not counted raw: the ledger is shared with migrations
|
|
14
|
+
// this manager does not own -- the vector ones -- and a raw count would report a version *above*
|
|
15
|
+
// the target on a database that has them, which reads as "schema ahead" rather than "ready".
|
|
16
|
+
const appliedIds = async () => {
|
|
17
|
+
const known = new Set(migrations.map((m) => m.id));
|
|
18
|
+
return new Set([...(await appliedMigrationIds(sql))].filter((id) => known.has(id)));
|
|
19
|
+
};
|
|
20
|
+
const pending = async () => {
|
|
21
|
+
const done = await appliedIds();
|
|
22
|
+
return migrations.filter((m) => !done.has(m.id));
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
async currentVersion() {
|
|
26
|
+
return (await appliedIds()).size;
|
|
27
|
+
},
|
|
28
|
+
targetVersion() {
|
|
29
|
+
return migrations.length;
|
|
30
|
+
},
|
|
31
|
+
async plan() {
|
|
32
|
+
return (await pending()).map((m) => ({ id: m.id, statements: m.up }));
|
|
33
|
+
},
|
|
34
|
+
async apply() {
|
|
35
|
+
await sql.query(MIGRATION_LEDGER);
|
|
36
|
+
for (const m of await pending()) {
|
|
37
|
+
for (const stmt of m.up)
|
|
38
|
+
await sql.query(stmt);
|
|
39
|
+
// ON CONFLICT DO NOTHING: two workers applying the same migration can't both record it.
|
|
40
|
+
await sql.query(`INSERT INTO schema_migrations (id) VALUES ($1) ON CONFLICT (id) DO NOTHING`, [m.id]);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Startup provisioning per mode. `off` (Postgres default) leaves the schema to managed
|
|
47
|
+
* migrations; `plan` logs the pending diff and applies nothing; `auto` provisions.
|
|
48
|
+
*/
|
|
49
|
+
export const provisionSchema = async (sql, options = {}) => {
|
|
50
|
+
const mode = options.mode ?? "off";
|
|
51
|
+
const manager = createSchemaManager(sql, options.migrations);
|
|
52
|
+
const log = options.log ?? (() => { });
|
|
53
|
+
if (mode === "off") {
|
|
54
|
+
return { mode, applied: [], planned: [] };
|
|
55
|
+
}
|
|
56
|
+
const planned = await manager.plan();
|
|
57
|
+
if (mode === "plan") {
|
|
58
|
+
if (planned.length === 0)
|
|
59
|
+
log("schema is up to date; no changes to apply");
|
|
60
|
+
else
|
|
61
|
+
log(`schema plan — ${planned.length} pending migration(s): ${planned.map((c) => c.id).join(", ")}`);
|
|
62
|
+
return { mode, applied: [], planned };
|
|
63
|
+
}
|
|
64
|
+
// auto
|
|
65
|
+
await manager.apply();
|
|
66
|
+
if (planned.length)
|
|
67
|
+
log(`provisioned schema: applied ${planned.map((c) => c.id).join(", ")}`);
|
|
68
|
+
return { mode, applied: planned.map((c) => c.id), planned };
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SessionStateStore, ThreadSummaryStore } from "../../persistence/index.js";
|
|
2
|
+
import type { SqlExecutor } from "./sql.js";
|
|
3
|
+
export declare const createPostgresSessionStateStore: (sql: SqlExecutor, options?: {
|
|
4
|
+
readonly maxBytes?: number;
|
|
5
|
+
}) => SessionStateStore;
|
|
6
|
+
export declare const createPostgresThreadSummaryStore: (sql: SqlExecutor) => ThreadSummaryStore;
|
|
7
|
+
//# sourceMappingURL=session-state.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL `SessionStateStore` and `ThreadSummaryStore` (#97) — cross-turn working memory and
|
|
3
|
+
* compacted history, the two pieces that replace Agno's fixed 20-turn re-injection.
|
|
4
|
+
*
|
|
5
|
+
* The optimistic-concurrency guard lives in the statement. `put` is one upsert whose `DO UPDATE`
|
|
6
|
+
* carries `WHERE session_state.version = $expectedVersion`; zero rows returned means the caller's
|
|
7
|
+
* version was stale. Read-then-write would reintroduce the lost-update race the `version` column
|
|
8
|
+
* exists to prevent — and two runs on one conversation is not hypothetical, it is precisely what the
|
|
9
|
+
* run coordinator (#98) exists to serialise.
|
|
10
|
+
*
|
|
11
|
+
* Note the deliberate asymmetry with `run_events` (#94): there the *caller* owns the sequence, because
|
|
12
|
+
* it is the client's reconnect cursor. Here the store allocates the version, because `append` takes
|
|
13
|
+
* none and returns the created one. Similar-looking tables, opposite correct answers.
|
|
14
|
+
*/
|
|
15
|
+
import { AgentPlatformError } from "../../core/errors.js";
|
|
16
|
+
import { DEFAULT_SESSION_STATE_MAX_BYTES } from "../../persistence/index.js";
|
|
17
|
+
const iso = (v) => (v instanceof Date ? v.toISOString() : new Date(v).toISOString());
|
|
18
|
+
const conflict = (m) => new AgentPlatformError({ code: "conflict", message: m, retryable: false });
|
|
19
|
+
const invalid = (m) => new AgentPlatformError({ code: "invalid_input", message: m, retryable: false });
|
|
20
|
+
const toSessionState = (r) => ({
|
|
21
|
+
conversationId: r.conversation_id,
|
|
22
|
+
version: r.version,
|
|
23
|
+
data: (typeof r.state === "string" ? JSON.parse(r.state) : r.state),
|
|
24
|
+
updatedAt: iso(r.updated_at),
|
|
25
|
+
});
|
|
26
|
+
export const createPostgresSessionStateStore = (sql, options = {}) => {
|
|
27
|
+
const maxBytes = options.maxBytes ?? DEFAULT_SESSION_STATE_MAX_BYTES;
|
|
28
|
+
return {
|
|
29
|
+
async get({ tenantId, conversationId }) {
|
|
30
|
+
const rows = await sql.query(`SELECT conversation_id, state, version, updated_at
|
|
31
|
+
FROM session_state WHERE tenant_id = $1 AND conversation_id = $2`, [tenantId, conversationId]);
|
|
32
|
+
const row = rows[0];
|
|
33
|
+
return row ? toSessionState(row) : null;
|
|
34
|
+
},
|
|
35
|
+
async put({ tenantId, conversationId, expectedVersion, data }) {
|
|
36
|
+
const encoded = JSON.stringify(data);
|
|
37
|
+
const bytes = Buffer.byteLength(encoded, "utf8");
|
|
38
|
+
// Checked before the write, and with the same message shape as the reference adapter, so the
|
|
39
|
+
// two agree on what is storable rather than only on the happy path.
|
|
40
|
+
if (bytes > maxBytes)
|
|
41
|
+
throw invalid(`Session state ${bytes}B exceeds the ${maxBytes}B ceiling`);
|
|
42
|
+
const rows = await sql.query(`INSERT INTO session_state (tenant_id, conversation_id, state, version, updated_at)
|
|
43
|
+
VALUES ($1, $2, $3::jsonb, 1, now())
|
|
44
|
+
ON CONFLICT (tenant_id, conversation_id) DO UPDATE
|
|
45
|
+
SET state = excluded.state,
|
|
46
|
+
version = session_state.version + 1,
|
|
47
|
+
updated_at = now()
|
|
48
|
+
-- The compare-and-set. A stale expectedVersion matches no row, so the write is refused
|
|
49
|
+
-- rather than silently overwriting a newer value.
|
|
50
|
+
WHERE session_state.version = $4
|
|
51
|
+
RETURNING conversation_id, state, version, updated_at`, [tenantId, conversationId, encoded, expectedVersion]);
|
|
52
|
+
const row = rows[0];
|
|
53
|
+
if (row)
|
|
54
|
+
return toSessionState(row);
|
|
55
|
+
// No row: either the caller expected 0 but a row exists (a first write racing a second), or a
|
|
56
|
+
// genuinely stale version. Read to say which, since the distinction is the caller's next move.
|
|
57
|
+
const current = await this.get({ tenantId, conversationId });
|
|
58
|
+
throw conflict(`Session state for ${conversationId} version ${expectedVersion} is stale (current ${current?.version ?? 0})`);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const toSummary = (r) => ({
|
|
63
|
+
conversationId: r.conversation_id,
|
|
64
|
+
version: r.version,
|
|
65
|
+
summary: r.summary,
|
|
66
|
+
coversUpToMessageId: r.covers_up_to_message_id,
|
|
67
|
+
createdAt: iso(r.created_at),
|
|
68
|
+
});
|
|
69
|
+
export const createPostgresThreadSummaryStore = (sql) => ({
|
|
70
|
+
async latest({ tenantId, conversationId }) {
|
|
71
|
+
const rows = await sql.query(`SELECT conversation_id, version, summary, covers_up_to_message_id, created_at
|
|
72
|
+
FROM thread_summaries
|
|
73
|
+
WHERE tenant_id = $1 AND conversation_id = $2
|
|
74
|
+
ORDER BY version DESC
|
|
75
|
+
LIMIT 1`, [tenantId, conversationId]);
|
|
76
|
+
const row = rows[0];
|
|
77
|
+
return row ? toSummary(row) : null;
|
|
78
|
+
},
|
|
79
|
+
async append({ tenantId, conversationId, summary, coversUpToMessageId }) {
|
|
80
|
+
// Version allocated in the INSERT rather than read-then-write, so two concurrent appends cannot
|
|
81
|
+
// both compute the same next version — the primary key would reject the loser, which is the
|
|
82
|
+
// behaviour we want, but computing it in-statement means the winner does not need a retry.
|
|
83
|
+
const rows = await sql.query(`INSERT INTO thread_summaries
|
|
84
|
+
(tenant_id, conversation_id, version, summary, covers_up_to_message_id, created_at)
|
|
85
|
+
SELECT $1, $2,
|
|
86
|
+
COALESCE(MAX(version), 0) + 1,
|
|
87
|
+
$3, $4, now()
|
|
88
|
+
FROM thread_summaries
|
|
89
|
+
WHERE tenant_id = $1 AND conversation_id = $2
|
|
90
|
+
RETURNING conversation_id, version, summary, covers_up_to_message_id, created_at`, [tenantId, conversationId, summary, coversUpToMessageId]);
|
|
91
|
+
const row = rows[0];
|
|
92
|
+
// Append is not optional: a caller that asked for a summary and got nothing back would have no
|
|
93
|
+
// way to know its compaction was lost.
|
|
94
|
+
if (!row)
|
|
95
|
+
throw conflict(`Could not append a thread summary for ${conversationId}`);
|
|
96
|
+
return toSummary(row);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=session-state.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal SQL executor the PostgreSQL adapters are written against, so the store code is pure
|
|
3
|
+
* SQL and imports no driver. Production wraps `pg.Pool` (`createPgExecutor`); tests wrap PGlite.
|
|
4
|
+
*/
|
|
5
|
+
export interface SqlExecutor {
|
|
6
|
+
query<Row = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<Row[]>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SqlExecutor } from "./sql.js";
|
|
2
|
+
/** Runs `fn` with every query inside one transaction. Nesting composes via savepoints. */
|
|
3
|
+
export interface TransactionRunner {
|
|
4
|
+
transaction<T>(fn: (sql: SqlExecutor) => Promise<T>): Promise<T>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Grants exclusive use of a single connection for the duration of `fn`. The one thing a pool-backed
|
|
8
|
+
* `SqlExecutor` cannot express, and the only primitive the runner needs from the driver.
|
|
9
|
+
*/
|
|
10
|
+
export type ConnectionOpener = <T>(fn: (sql: SqlExecutor) => Promise<T>) => Promise<T>;
|
|
11
|
+
export type TransactionScope = {
|
|
12
|
+
readonly runner: TransactionRunner;
|
|
13
|
+
/**
|
|
14
|
+
* Wrap a base executor so its queries join the ambient transaction when one is running. Stores
|
|
15
|
+
* built over this participate in a `UnitOfWork` without knowing transactions exist.
|
|
16
|
+
*/
|
|
17
|
+
readonly scoped: (base: SqlExecutor) => SqlExecutor;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A transaction scope over one connection source. The `AsyncLocalStorage` is private to the scope, so
|
|
21
|
+
* two scopes over two different databases cannot pick up each other's transaction — a real hazard
|
|
22
|
+
* when a process holds several executors, as the conformance suite does.
|
|
23
|
+
*/
|
|
24
|
+
export declare const createTransactionScope: (open: ConnectionOpener) => TransactionScope;
|
|
25
|
+
/**
|
|
26
|
+
* Opener over a single shared connection — PGlite, and any other single-connection executor.
|
|
27
|
+
*
|
|
28
|
+
* Serialised deliberately. One connection cannot hold two transactions, so concurrent callers must
|
|
29
|
+
* queue or their `BEGIN`/`COMMIT` pairs interleave into nonsense. The conformance suite fires four
|
|
30
|
+
* concurrent claims at the coordinator, so this is exercised rather than theoretical.
|
|
31
|
+
*
|
|
32
|
+
* The consequence to keep in mind: on this opener, concurrency is *simulated*. It proves ordering and
|
|
33
|
+
* single-flight, never that the database adjudicates a genuine race — which is why #98's AC-1 has a
|
|
34
|
+
* separate two-connection test that only runs against a real server.
|
|
35
|
+
*/
|
|
36
|
+
export declare const createSingleConnectionOpener: (sql: SqlExecutor) => ConnectionOpener;
|
|
37
|
+
//# sourceMappingURL=transaction.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transactions for the PostgreSQL adapters (#98).
|
|
3
|
+
*
|
|
4
|
+
* **Why this file has to exist.** `SqlExecutor` is `{ query(text, params) }`, and in production it
|
|
5
|
+
* wraps `pg.Pool` — where `pool.query` takes *a different connection each call*. So `BEGIN`, the
|
|
6
|
+
* work, and `COMMIT` issued as three `query` calls land on three connections and guarantee nothing.
|
|
7
|
+
* Every transactional promise in this SPEC is unreachable through the bare interface.
|
|
8
|
+
*
|
|
9
|
+
* It appears to work under PGlite because PGlite is one embedded connection, so `BEGIN` on it happens
|
|
10
|
+
* to scope everything after — which is what #97's AC-3 test relied on. Correct locally, wrong on a
|
|
11
|
+
* real server. That is the class of bug the CI Postgres service exists to catch.
|
|
12
|
+
*
|
|
13
|
+
* **Why the transaction is ambient.** The port is `UnitOfWork.run<T>(fn: () => Promise<T>)` — the
|
|
14
|
+
* callback receives *nothing*. A store constructed before the transaction has no parameter through
|
|
15
|
+
* which it could be told about one, and the conformance harness constructs it exactly that way. So
|
|
16
|
+
* either the port widens (a bigger decision than this SPEC) or the transaction is discovered from the
|
|
17
|
+
* async context. `AsyncLocalStorage` is that mechanism: `scoped()` wraps a base executor so it uses
|
|
18
|
+
* the ambient transaction's connection while one is running, and the pool otherwise.
|
|
19
|
+
*
|
|
20
|
+
* The cost of ambient propagation is honest to state: it is invisible at the call site. A store built
|
|
21
|
+
* over a *non*-scoped executor silently escapes the transaction rather than failing. See the open
|
|
22
|
+
* question on #98.
|
|
23
|
+
*/
|
|
24
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
25
|
+
/**
|
|
26
|
+
* A transaction scope over one connection source. The `AsyncLocalStorage` is private to the scope, so
|
|
27
|
+
* two scopes over two different databases cannot pick up each other's transaction — a real hazard
|
|
28
|
+
* when a process holds several executors, as the conformance suite does.
|
|
29
|
+
*/
|
|
30
|
+
export const createTransactionScope = (open) => {
|
|
31
|
+
const active = new AsyncLocalStorage();
|
|
32
|
+
const runner = {
|
|
33
|
+
async transaction(fn) {
|
|
34
|
+
const current = active.getStore();
|
|
35
|
+
// Nested: a savepoint on the connection we are already holding. Taking a second connection
|
|
36
|
+
// here would deadlock against the row locks the outer transaction is holding.
|
|
37
|
+
if (current) {
|
|
38
|
+
const name = `agentkit_sp_${current.depth}`;
|
|
39
|
+
await current.sql.query(`SAVEPOINT ${name}`);
|
|
40
|
+
try {
|
|
41
|
+
const result = await active.run({ sql: current.sql, depth: current.depth + 1 }, () => fn(current.sql));
|
|
42
|
+
await current.sql.query(`RELEASE SAVEPOINT ${name}`);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// Best-effort: if the rollback itself fails the connection is already unusable, and the
|
|
47
|
+
// original error is the one worth propagating.
|
|
48
|
+
await current.sql.query(`ROLLBACK TO SAVEPOINT ${name}`).catch(() => undefined);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return open(async (sql) => {
|
|
53
|
+
await sql.query("BEGIN");
|
|
54
|
+
try {
|
|
55
|
+
const result = await active.run({ sql, depth: 1 }, () => fn(sql));
|
|
56
|
+
await sql.query("COMMIT");
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
await sql.query("ROLLBACK").catch(() => undefined);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const scoped = (base) => ({
|
|
67
|
+
query(text, params) {
|
|
68
|
+
return (active.getStore()?.sql ?? base).query(text, params);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return { runner, scoped };
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Opener over a single shared connection — PGlite, and any other single-connection executor.
|
|
75
|
+
*
|
|
76
|
+
* Serialised deliberately. One connection cannot hold two transactions, so concurrent callers must
|
|
77
|
+
* queue or their `BEGIN`/`COMMIT` pairs interleave into nonsense. The conformance suite fires four
|
|
78
|
+
* concurrent claims at the coordinator, so this is exercised rather than theoretical.
|
|
79
|
+
*
|
|
80
|
+
* The consequence to keep in mind: on this opener, concurrency is *simulated*. It proves ordering and
|
|
81
|
+
* single-flight, never that the database adjudicates a genuine race — which is why #98's AC-1 has a
|
|
82
|
+
* separate two-connection test that only runs against a real server.
|
|
83
|
+
*/
|
|
84
|
+
export const createSingleConnectionOpener = (sql) => {
|
|
85
|
+
let tail = Promise.resolve();
|
|
86
|
+
return (fn) => {
|
|
87
|
+
// Chained off both outcomes: a rejected predecessor must not strand every later caller.
|
|
88
|
+
const result = tail.then(() => fn(sql), () => fn(sql));
|
|
89
|
+
tail = result.then(() => undefined, () => undefined);
|
|
90
|
+
return result;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=transaction.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL `UnitOfWork` (#98) — the mechanism that makes "usage and session state written
|
|
3
|
+
* atomically in the completion transaction" true rather than aspirational.
|
|
4
|
+
*
|
|
5
|
+
* Thin on purpose. All of the substance is in `transaction.ts`: the runner holds one connection for
|
|
6
|
+
* the duration and binds it to the async context, so stores built over a `scoped()` executor join the
|
|
7
|
+
* transaction without being passed anything. This function only has to translate the port's
|
|
8
|
+
* zero-argument `run` onto that.
|
|
9
|
+
*
|
|
10
|
+
* The reference in-memory adapter cannot do this — it offers caller-registered compensations through
|
|
11
|
+
* its own `runTx`, which the bare port cannot express, which is why `unitOfWorkConformance` gates the
|
|
12
|
+
* rollback case on the `transactions` capability. Postgres is the first adapter for which that gate
|
|
13
|
+
* opens, so that case now runs for real instead of standing down everywhere.
|
|
14
|
+
*/
|
|
15
|
+
import type { UnitOfWork } from "../../persistence/index.js";
|
|
16
|
+
import type { TransactionRunner } from "./transaction.js";
|
|
17
|
+
export declare const createPostgresUnitOfWork: (runner: TransactionRunner) => UnitOfWork;
|
|
18
|
+
//# sourceMappingURL=unit-of-work.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const createPostgresUnitOfWork = (runner) => ({
|
|
2
|
+
run(fn) {
|
|
3
|
+
// The executor is deliberately dropped: the port gives `fn` no parameter to receive it through,
|
|
4
|
+
// so propagation is ambient (see transaction.ts). Nested `run` calls become savepoints.
|
|
5
|
+
return runner.transaction(() => fn());
|
|
6
|
+
},
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=unit-of-work.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL `UsageLimitStore` — admin-configured spend limits (#175).
|
|
3
|
+
*
|
|
4
|
+
* `principal_id IS NULL` is the tenant default; a non-null row overrides it for one person. One table rather than
|
|
5
|
+
* two, because "the default" and "an override" are the same kind of fact — and resolution is then a single query
|
|
6
|
+
* ordered by specificity rather than two queries and a merge in application code.
|
|
7
|
+
*
|
|
8
|
+
* Every limit column is nullable and NULL means **unbounded**, not zero. That direction matches `QuotaLimits` and
|
|
9
|
+
* is the whole reason it is stated twice: a misconfigured quota that blocks everything is an outage, and one that
|
|
10
|
+
* blocks nothing is a bill the rollups make visible. An outage is only visible to the customer it happens to.
|
|
11
|
+
*/
|
|
12
|
+
import type { UsageLimitStore } from "../../persistence/index.js";
|
|
13
|
+
import type { SqlExecutor } from "./sql.js";
|
|
14
|
+
export declare const createPostgresUsageLimitStore: (sql: SqlExecutor) => UsageLimitStore;
|
|
15
|
+
//# sourceMappingURL=usage-limits.d.ts.map
|