experimental-a2 0.19.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +1 -1
- package/dist/{actor-C7b-1arn.d.ts → actor-HnGmrOZZ.d.ts} +5 -5
- package/dist/{actor-C7b-1arn.d.ts.map → actor-HnGmrOZZ.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-client.js +2 -2
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +5 -5
- package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
- package/dist/{actor-shared-t7w8xIKP.js.map → actor-shared-DFET5q16.js.map} +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +54 -43
- package/dist/actor.js.map +1 -1
- package/dist/{ai-Cz3hqVhk.d.ts → ai-CvEnqVfo.d.ts} +8 -8
- package/dist/{ai-Cz3hqVhk.d.ts.map → ai-CvEnqVfo.d.ts.map} +1 -1
- package/dist/{ai-mMiWul1T.js → ai-CzE672Q3.js} +9 -9
- package/dist/ai-CzE672Q3.js.map +1 -0
- package/dist/ai-client.d.ts +2 -2
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.d.ts +1 -1
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +36 -9
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-Bfk-Lycn.d.ts} +2 -2
- package/dist/ai-harness-Bfk-Lycn.d.ts.map +1 -0
- package/dist/{ai-harness-BuuC-gJ3.js → ai-harness-dQeJaR1t.js} +17 -4
- package/dist/ai-harness-dQeJaR1t.js.map +1 -0
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.js +4 -4
- package/dist/ai-pi.js.map +1 -1
- package/dist/ai-react.d.ts +5 -5
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +14 -17
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +84 -75
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BhHLoNKF.js → client-9IeTsiAs.js} +5 -5
- package/dist/{client-BhHLoNKF.js.map → client-9IeTsiAs.js.map} +1 -1
- package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
- package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
- package/dist/contract-CWukt01d.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
- package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
- package/dist/{internal-CscSY7K_.js → internal-Bc4PvfHk.js} +10 -3
- package/dist/internal-Bc4PvfHk.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/otel.js +1 -1
- package/dist/otel.js.map +1 -1
- package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
- package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
- package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
- package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
- package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
- package/dist/runtime-consumer-BWmht60z.js +12 -0
- package/dist/runtime-consumer-BWmht60z.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +7 -7
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-task-X1SToDjP.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +5 -4
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-DYAATFuT.d.ts → server-BshFZ0eL.d.ts} +108 -41
- package/dist/server-BshFZ0eL.d.ts.map +1 -0
- package/dist/{server-D-Y4pqwu.js → server-DvIB8kj5.js} +423 -135
- package/dist/server-DvIB8kj5.js.map +1 -0
- package/dist/server.browser.js +1 -1
- package/dist/server.browser.js.map +1 -1
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-Bk_29eEw.d.ts} +2 -2
- package/dist/session-clear-Bk_29eEw.d.ts.map +1 -0
- package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
- package/dist/session-clear-wHd5GCpZ.js.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-Z2qjw50n.d.ts +523 -0
- package/dist/store-Z2qjw50n.d.ts.map +1 -0
- package/dist/store-codec-DwrWlgQx.js +65 -0
- package/dist/store-codec-DwrWlgQx.js.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +393 -210
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-7WJV0YWP.js → store-polling-DYNyxCev.js} +2 -2
- package/dist/{store-polling-7WJV0YWP.js.map → store-polling-DYNyxCev.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +1595 -673
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-B1ixeqIS.js → store-presence-polling-CzQ0wdtM.js} +3 -3
- package/dist/{store-presence-polling-B1ixeqIS.js.map → store-presence-polling-CzQ0wdtM.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +4 -4
- package/dist/store-redis-notify-IiVdlYHc.js +2919 -0
- package/dist/store-redis-notify-IiVdlYHc.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +2 -2
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +517 -287
- package/dist/store-sqlite.js.map +1 -1
- package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
- package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
- package/dist/{wire-BKxH1vbP.js → wire-CZGqNZRx.js} +5 -4
- package/dist/wire-CZGqNZRx.js.map +1 -0
- package/docs/01-quickstart.mdx +28 -19
- package/docs/02-examples.mdx +1 -1
- package/docs/actors/01-introduction.mdx +9 -1
- package/docs/actors/02-concurrency.mdx +1 -1
- package/docs/actors/meta.ts +1 -1
- package/docs/agents/01-introduction.mdx +1 -1
- package/docs/agents/02-tools.mdx +13 -12
- package/docs/agents/03-conversations.mdx +31 -21
- package/docs/agents/04-harnesses.mdx +7 -3
- package/docs/agents/meta.ts +1 -1
- package/docs/client/03-presence.mdx +18 -14
- package/docs/client/meta.ts +1 -1
- package/docs/contract/01-events.mdx +2 -2
- package/docs/contract/02-state.mdx +18 -14
- package/docs/contract/03-sessions.mdx +32 -25
- package/docs/index.mdx +31 -26
- package/docs/reference/01-core.mdx +12 -12
- package/docs/reference/02-server.mdx +247 -192
- package/docs/reference/04-ai.mdx +29 -20
- package/docs/reference/05-telemetry.mdx +146 -0
- package/docs/reference/06-errors.mdx +24 -18
- package/docs/reference/meta.ts +1 -1
- package/docs/schedulers/01-overview.mdx +112 -0
- package/docs/schedulers/02-vercel-queues.mdx +87 -0
- package/docs/schedulers/03-qstash.mdx +123 -0
- package/docs/schedulers/meta.ts +1 -0
- package/docs/server/01-consumers.mdx +415 -0
- package/docs/server/02-timers.mdx +33 -23
- package/docs/server/03-cancellation.mdx +52 -44
- package/docs/server/04-routes.mdx +4 -1
- package/docs/server/05-sql-projections.mdx +391 -0
- package/docs/server/06-guarantees.mdx +60 -50
- package/docs/stores/01-overview.mdx +37 -0
- package/docs/stores/02-redis.mdx +57 -0
- package/docs/stores/03-redis-http.mdx +56 -0
- package/docs/stores/04-postgres.mdx +172 -0
- package/docs/stores/05-sqlite.mdx +36 -0
- package/docs/stores/06-memory.mdx +22 -0
- package/docs/stores/meta.ts +1 -0
- package/docs/upgrades/01-overview.mdx +30 -0
- package/docs/upgrades/79-0-21.mdx +165 -0
- package/docs/upgrades/80-0-20.mdx +43 -0
- package/docs/upgrades/81-0-19.mdx +33 -0
- package/docs/upgrades/82-0-18.mdx +33 -0
- package/docs/upgrades/83-0-17.mdx +69 -0
- package/docs/upgrades/meta.ts +3 -0
- package/examples/playground/DEPLOY.md +48 -11
- package/examples/playground/app/a0/apps/[appId]/route.ts +6 -10
- package/examples/playground/app/a0/chat.test.tsx +421 -330
- package/examples/playground/app/a0/sandbox-log.ts +17 -15
- package/examples/playground/app/a0/server.ts +4 -4
- package/examples/playground/app/a0/shell.tsx +1 -1
- package/examples/playground/app/a0/uploads/route.test.ts +59 -0
- package/examples/playground/app/a2slack/a2slack.css +1067 -0
- package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
- package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
- package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
- package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
- package/examples/playground/app/a2slack/consumers/search.ts +37 -0
- package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
- package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
- package/examples/playground/app/a2slack/db/a2.ts +206 -0
- package/examples/playground/app/a2slack/db/index.ts +73 -0
- package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
- package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
- package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
- package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +34 -0
- package/examples/playground/app/a2slack/db/queries.ts +263 -0
- package/examples/playground/app/a2slack/db/schema.ts +145 -0
- package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
- package/examples/playground/app/a2slack/drills/harness.ts +244 -0
- package/examples/playground/app/a2slack/events/route.ts +36 -0
- package/examples/playground/app/a2slack/identity.ts +44 -0
- package/examples/playground/app/a2slack/inbox/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/page.tsx +101 -0
- package/examples/playground/app/a2slack/lag.ts +73 -0
- package/examples/playground/app/a2slack/layout.tsx +43 -0
- package/examples/playground/app/a2slack/messages/route.ts +17 -0
- package/examples/playground/app/a2slack/model.test.ts +208 -0
- package/examples/playground/app/a2slack/model.ts +369 -0
- package/examples/playground/app/a2slack/models.test.ts +72 -0
- package/examples/playground/app/a2slack/page.tsx +10 -0
- package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
- package/examples/playground/app/a2slack/search/route.ts +40 -0
- package/examples/playground/app/a2slack/search-index.ts +108 -0
- package/examples/playground/app/a2slack/server.test.ts +315 -0
- package/examples/playground/app/a2slack/server.ts +55 -0
- package/examples/playground/app/a2slack/sessions.ts +50 -0
- package/examples/playground/app/a2slack/thread/route.ts +8 -0
- package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
- package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
- package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
- package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
- package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
- package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
- package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
- package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
- package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
- package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
- package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
- package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
- package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
- package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
- package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
- package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +116 -90
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +107 -76
- package/examples/playground/app/agent/ingress.test.ts +41 -17
- package/examples/playground/app/agent/model-input.test.tsx +92 -43
- package/examples/playground/app/agent/server.ts +3 -3
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +2 -2
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
- package/examples/playground/app/chat/server.test.ts +38 -65
- package/examples/playground/app/chat/server.ts +45 -35
- package/examples/playground/app/components/activity-history.test.tsx +60 -18
- package/examples/playground/app/components/demo-icons.tsx +7 -0
- package/examples/playground/app/components/demos.tsx +7 -0
- package/examples/playground/app/components/fast-link.test.tsx +3 -5
- package/examples/playground/app/initial-state.test.tsx +86 -45
- package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
- package/examples/playground/app/orders/page.tsx +1 -1
- package/examples/playground/app/orders/server.ts +49 -30
- package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
- package/examples/playground/app/recovery/page.tsx +4 -4
- package/examples/playground/app/recovery/server.ts +23 -32
- package/examples/playground/drizzle.config.ts +14 -0
- package/examples/playground/next.config.ts +6 -1
- package/examples/playground/package.json +13 -1
- package/examples/playground/scripts/migrate.mjs +31 -0
- package/examples/playground/test/agent.ts +35 -0
- package/examples/playground/test/model.ts +149 -0
- package/examples/playground/test/serve.ts +184 -0
- package/examples/playground/test/server-only.ts +1 -0
- package/examples/playground/vercel.json +9 -0
- package/examples/playground/vitest.config.ts +27 -0
- package/examples/playground/vitest.drills.config.ts +32 -0
- package/package.json +7 -2
- package/sql/postgres/drizzle-schema.ts +206 -0
- package/sql/postgres/notifications.sql +24 -0
- package/sql/postgres/schema.sql +276 -0
- package/src/actor.ts +101 -71
- package/src/ai-agent.ts +154 -115
- package/src/ai-control-server.ts +8 -8
- package/src/ai-fx.ts +41 -9
- package/src/ai-harness.ts +28 -2
- package/src/ai-pi.ts +2 -2
- package/src/ai-server.ts +2 -2
- package/src/ai.ts +7 -7
- package/src/contract.ts +1 -1
- package/src/deterministic-id.ts +26 -3
- package/src/errors.ts +9 -6
- package/src/idempotent-replay.ts +0 -1
- package/src/index.ts +1 -1
- package/src/internal.ts +17 -6
- package/src/otel.ts +1 -1
- package/src/postgres-notifications.ts +0 -22
- package/src/postgres-resources.ts +6 -9
- package/src/postgres-schema.ts +121 -0
- package/src/push-envelope.ts +20 -0
- package/src/runtime-consumer.ts +10 -0
- package/src/scheduler-qstash.ts +12 -5
- package/src/scheduler-task.ts +2 -2
- package/src/scheduler-vercel.ts +10 -4
- package/src/server.browser.ts +1 -1
- package/src/server.ts +839 -238
- package/src/session-socket.ts +1 -1
- package/src/store-codec.ts +66 -0
- package/src/store-memory.ts +581 -255
- package/src/store-postgres.ts +1984 -943
- package/src/store-redis-core.ts +1624 -1152
- package/src/store-redis-notify.ts +2 -0
- package/src/store-sqlite.ts +891 -486
- package/src/store.ts +227 -97
- package/src/telemetry.ts +2 -1
- package/src/wire.ts +1 -0
- package/dist/ai-harness-BuuC-gJ3.js.map +0 -1
- package/dist/ai-harness-CV6rXbB3.d.ts.map +0 -1
- package/dist/ai-mMiWul1T.js.map +0 -1
- package/dist/contract-BYKASlCV.js.map +0 -1
- package/dist/idempotent-replay-JdgMQRRm.js +0 -21
- package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
- package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
- package/dist/internal-CscSY7K_.js.map +0 -1
- package/dist/server-D-Y4pqwu.js.map +0 -1
- package/dist/server-DYAATFuT.d.ts.map +0 -1
- package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
- package/dist/session-clear-DGI4jTQ5.js.map +0 -1
- package/dist/store-CyLUy86v.d.ts +0 -404
- package/dist/store-CyLUy86v.d.ts.map +0 -1
- package/dist/store-codec-DTG0Ftek.js +0 -10
- package/dist/store-codec-DTG0Ftek.js.map +0 -1
- package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
- package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
- package/dist/wire-BKxH1vbP.js.map +0 -1
- package/docs/reference/05-adapters.mdx +0 -351
- package/docs/server/01-handlers.mdx +0 -200
- package/docs/server/05-production.mdx +0 -323
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
import { vercelQueues } from 'experimental-a2/scheduler-vercel'
|
|
3
|
-
import { createServer } from 'experimental-a2/server'
|
|
3
|
+
import { consumer, createServer } from 'experimental-a2/server'
|
|
4
4
|
import { store } from '@/lib/store'
|
|
5
5
|
import { recovery } from './model'
|
|
6
6
|
|
|
@@ -15,39 +15,30 @@ export const recoveryScheduler =
|
|
|
15
15
|
? vercelQueues({ topic: 'a2-recovery' })
|
|
16
16
|
: undefined
|
|
17
17
|
|
|
18
|
+
/** Each step waits its 8 seconds, then returns the next one, in one lane. */
|
|
19
|
+
const runPipeline = consumer({
|
|
20
|
+
contract: recovery,
|
|
21
|
+
name: 'run-pipeline',
|
|
22
|
+
types: ['pipeline.started', 'step.completed'],
|
|
23
|
+
lane: 'pipeline',
|
|
24
|
+
}).run(async ({ event }) => {
|
|
25
|
+
const step = event.type === 'step.completed' ? event.payload.step : 0
|
|
26
|
+
if (step === stepCount) {
|
|
27
|
+
return {
|
|
28
|
+
type: 'pipeline.completed',
|
|
29
|
+
payload: { stepCount },
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
await wait(stepDurationSeconds * 1_000)
|
|
33
|
+
return {
|
|
34
|
+
type: 'step.completed',
|
|
35
|
+
payload: { step: step + 1, durationSeconds: stepDurationSeconds },
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
18
39
|
export const recoveryServer = createServer({
|
|
19
40
|
...(store ? { store } : {}),
|
|
20
41
|
...(recoveryScheduler ? { scheduler: recoveryScheduler } : {}),
|
|
21
42
|
contract: recovery,
|
|
22
|
-
|
|
23
|
-
'pipeline.started': {
|
|
24
|
-
lane: 'pipeline',
|
|
25
|
-
handler: async () => {
|
|
26
|
-
await wait(stepDurationSeconds * 1_000)
|
|
27
|
-
return {
|
|
28
|
-
type: 'step.completed',
|
|
29
|
-
payload: { step: 1, durationSeconds: stepDurationSeconds },
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
'step.completed': {
|
|
34
|
-
lane: 'pipeline',
|
|
35
|
-
handler: async ({ event }) => {
|
|
36
|
-
if (event.payload.step === stepCount) {
|
|
37
|
-
return {
|
|
38
|
-
type: 'pipeline.completed',
|
|
39
|
-
payload: { stepCount },
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
await wait(stepDurationSeconds * 1_000)
|
|
43
|
-
return {
|
|
44
|
-
type: 'step.completed',
|
|
45
|
-
payload: {
|
|
46
|
-
step: event.payload.step + 1,
|
|
47
|
-
durationSeconds: stepDurationSeconds,
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
43
|
+
consumers: [runPipeline],
|
|
53
44
|
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The Postgres at DATABASE_URL: a2slack's tables, and A2's (db/a2.ts, a copy
|
|
2
|
+
// of experimental-a2's drizzle-schema.ts). tablesFilter keeps push and
|
|
3
|
+
// studio to these, since other apps share the database.
|
|
4
|
+
import { defineConfig } from 'drizzle-kit'
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
dialect: 'postgresql',
|
|
8
|
+
schema: './app/a2slack/db/schema.ts',
|
|
9
|
+
out: './app/a2slack/db/migrations',
|
|
10
|
+
casing: 'snake_case',
|
|
11
|
+
dbCredentials: { url: process.env.DATABASE_URL ?? '' },
|
|
12
|
+
tablesFilter: ['a2slack_*', 'a2_*'],
|
|
13
|
+
migrations: { table: 'a2slack_migrations', schema: 'public' },
|
|
14
|
+
})
|
|
@@ -12,7 +12,12 @@ const nextConfig: NextConfig = {
|
|
|
12
12
|
// Turbopack panic on its WebAssembly), so it finds photon through this
|
|
13
13
|
// app: package.json depends on photon at the version Pi pins, and the two
|
|
14
14
|
// move together.
|
|
15
|
-
serverExternalPackages: [
|
|
15
|
+
serverExternalPackages: [
|
|
16
|
+
'libfx',
|
|
17
|
+
'@silvia-odwyer/photon-node',
|
|
18
|
+
// a2slack's development database, PGlite, loads its WebAssembly by path.
|
|
19
|
+
'@electric-sql/pglite',
|
|
20
|
+
],
|
|
16
21
|
turbopack: {
|
|
17
22
|
// WebAssembly Pi's code reaches that the bundler cannot trace.
|
|
18
23
|
resolveAlias: {
|
|
@@ -11,18 +11,26 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"dev": "next dev",
|
|
13
13
|
"build": "next build",
|
|
14
|
+
"vercel-build": "node scripts/migrate.mjs && turbo run build",
|
|
14
15
|
"test": "vitest run",
|
|
16
|
+
"drills": "vitest run --config vitest.drills.config.ts",
|
|
15
17
|
"typecheck": "tsc --noEmit",
|
|
18
|
+
"db:generate": "drizzle-kit generate",
|
|
19
|
+
"db:migrate": "node scripts/migrate.mjs",
|
|
16
20
|
"a0:template": "node --env-file-if-exists=.env.local scripts/a0-template.ts"
|
|
17
21
|
},
|
|
18
22
|
"dependencies": {
|
|
19
23
|
"@ai-sdk/otel": "1.0.58",
|
|
24
|
+
"@base-ui/react": "^1.7.0",
|
|
20
25
|
"@codemirror/state": "^6.7.1",
|
|
21
26
|
"@codemirror/view": "^6.43.8",
|
|
27
|
+
"@electric-sql/pglite": "^0.3.14",
|
|
22
28
|
"@earendil-works/pi-ai": "1.0.3",
|
|
23
29
|
"@earendil-works/pi-coding-agent": "1.0.2",
|
|
24
30
|
"@opentelemetry/api": "^1.9.0",
|
|
31
|
+
"@paper-design/shaders-react": "^0.0.80",
|
|
25
32
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
33
|
+
"@upstash/redis": "^1.39.0",
|
|
26
34
|
"@vercel/blob": "^2.8.0",
|
|
27
35
|
"@vercel/functions": "^3.9.3",
|
|
28
36
|
"@vercel/otel": "^2.1.0",
|
|
@@ -30,7 +38,8 @@
|
|
|
30
38
|
"@vercel/sandbox": "^3.0.0",
|
|
31
39
|
"ai": "^7.0.58",
|
|
32
40
|
"codemirror": "^6.0.2",
|
|
33
|
-
"
|
|
41
|
+
"drizzle-orm": "^0.45.3",
|
|
42
|
+
"experimental-a2": "0.21.1",
|
|
34
43
|
"geist": "^1.7.2",
|
|
35
44
|
"ioredis": "^5.9.0",
|
|
36
45
|
"libfx": "0.0.12",
|
|
@@ -52,10 +61,13 @@
|
|
|
52
61
|
"@testing-library/react": "^16.1.0",
|
|
53
62
|
"jsdom": "^26.0.0",
|
|
54
63
|
"@types/node": "^24.0.0",
|
|
64
|
+
"@types/pg": "^8.15.0",
|
|
55
65
|
"@types/react": "^19.0.0",
|
|
56
66
|
"@types/react-dom": "^19.0.0",
|
|
57
67
|
"@types/ws": "^8.18.1",
|
|
68
|
+
"drizzle-kit": "^0.31.11",
|
|
58
69
|
"typescript": "^7.0.2",
|
|
70
|
+
"undici": "^8.10.2",
|
|
59
71
|
"vitest": "^4.1.10"
|
|
60
72
|
}
|
|
61
73
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies app/a2slack/db/migrations to the Postgres at DATABASE_URL_UNPOOLED
|
|
3
|
+
* (the Neon integration's direct connection) or DATABASE_URL. drizzle-kit
|
|
4
|
+
* migrate does the same, but hides the database's error when one fails.
|
|
5
|
+
*
|
|
6
|
+
* DATABASE_URL=postgres://… pnpm --filter playground db:migrate
|
|
7
|
+
*/
|
|
8
|
+
import { drizzle } from 'drizzle-orm/node-postgres'
|
|
9
|
+
import { migrate } from 'drizzle-orm/node-postgres/migrator'
|
|
10
|
+
|
|
11
|
+
const url = process.env.DATABASE_URL_UNPOOLED || process.env.DATABASE_URL
|
|
12
|
+
if (!url) {
|
|
13
|
+
console.error('error: set DATABASE_URL to the Postgres to migrate')
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const db = drizzle({ connection: url })
|
|
18
|
+
try {
|
|
19
|
+
await migrate(db, {
|
|
20
|
+
migrationsFolder: new URL('../app/a2slack/db/migrations', import.meta.url)
|
|
21
|
+
.pathname,
|
|
22
|
+
migrationsTable: 'a2slack_migrations',
|
|
23
|
+
migrationsSchema: 'public',
|
|
24
|
+
})
|
|
25
|
+
console.log('migrations applied')
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('error: migrations failed, and none were applied:', error)
|
|
28
|
+
process.exitCode = 1
|
|
29
|
+
} finally {
|
|
30
|
+
await db.$client.end()
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { agent } from 'experimental-a2/ai/server'
|
|
2
|
+
import { memory } from 'experimental-a2/store-memory'
|
|
3
|
+
import { demoAgent } from '@/app/agent/model'
|
|
4
|
+
import { authorizer, serve } from './serve'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The playground's agent on a memory store, served where the page's own
|
|
8
|
+
* client finds it: `/agent/events` on the origin relative URLs resolve
|
|
9
|
+
* against. Its tests never start a turn, so its model never runs.
|
|
10
|
+
*/
|
|
11
|
+
export async function serveAgent() {
|
|
12
|
+
const server = agent({
|
|
13
|
+
contract: demoAgent,
|
|
14
|
+
store: memory(),
|
|
15
|
+
model: 'test-model',
|
|
16
|
+
})
|
|
17
|
+
const probe = authorizer()
|
|
18
|
+
const served = await serve({
|
|
19
|
+
routes: {
|
|
20
|
+
'/agent/events': (request) =>
|
|
21
|
+
server.fetch(request, { authorize: probe.authorize }),
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
return {
|
|
25
|
+
server,
|
|
26
|
+
probe,
|
|
27
|
+
url: `${served.origin}/agent/events`,
|
|
28
|
+
close: async (): Promise<void> => {
|
|
29
|
+
probe.release()
|
|
30
|
+
await served.close()
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ServedAgent = Awaited<ReturnType<typeof serveAgent>>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model behind an agent under test. Agents take AI Gateway model IDs;
|
|
3
|
+
* the AI SDK's global provider serves every one of them from this model,
|
|
4
|
+
* whose steps the test writes part by part.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { MockLanguageModelV4 } from 'ai/test'
|
|
8
|
+
import { vi } from 'vitest'
|
|
9
|
+
|
|
10
|
+
export type StreamPart =
|
|
11
|
+
Awaited<
|
|
12
|
+
ReturnType<MockLanguageModelV4['doStream']>
|
|
13
|
+
>['stream'] extends ReadableStream<infer Part>
|
|
14
|
+
? Part
|
|
15
|
+
: never
|
|
16
|
+
|
|
17
|
+
export type CallOptions = MockLanguageModelV4['doStreamCalls'][number]
|
|
18
|
+
|
|
19
|
+
/** One model step, open until the test ends it or the agent aborts it. */
|
|
20
|
+
export type ModelStep = {
|
|
21
|
+
/** What the agent sent the model for this step. */
|
|
22
|
+
options: CallOptions
|
|
23
|
+
write(...parts: StreamPart[]): void
|
|
24
|
+
/** Ends the step the way a model does: with a finish part. */
|
|
25
|
+
finish(reason?: 'stop' | 'tool-calls'): void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const usage = {
|
|
29
|
+
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
|
|
30
|
+
outputTokens: { total: 1, text: 1, reasoning: 0 },
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A text part, whole. */
|
|
34
|
+
export const say = ({ id, text }: { id: string; text: string }) =>
|
|
35
|
+
[
|
|
36
|
+
{ type: 'text-start', id },
|
|
37
|
+
{ type: 'text-delta', id, delta: text },
|
|
38
|
+
{ type: 'text-end', id },
|
|
39
|
+
] satisfies StreamPart[]
|
|
40
|
+
|
|
41
|
+
/** A tool call whose input streams in as the given chunks of its JSON. */
|
|
42
|
+
export const toolCall = ({
|
|
43
|
+
id,
|
|
44
|
+
name,
|
|
45
|
+
chunks,
|
|
46
|
+
}: {
|
|
47
|
+
id: string
|
|
48
|
+
name: string
|
|
49
|
+
chunks: string[]
|
|
50
|
+
}) =>
|
|
51
|
+
[
|
|
52
|
+
{ type: 'tool-input-start', id, toolName: name },
|
|
53
|
+
...chunks.map((delta) => ({
|
|
54
|
+
type: 'tool-input-delta' as const,
|
|
55
|
+
id,
|
|
56
|
+
delta,
|
|
57
|
+
})),
|
|
58
|
+
{ type: 'tool-input-end', id },
|
|
59
|
+
{
|
|
60
|
+
type: 'tool-call',
|
|
61
|
+
toolCallId: id,
|
|
62
|
+
toolName: name,
|
|
63
|
+
input: chunks.join(''),
|
|
64
|
+
},
|
|
65
|
+
] satisfies StreamPart[]
|
|
66
|
+
|
|
67
|
+
let models = 0
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Installs a model the test drives step by step, served for `id` alone, so
|
|
71
|
+
* an agent left over from an earlier test can't take its steps. Remove it
|
|
72
|
+
* with `vi.unstubAllGlobals()`.
|
|
73
|
+
*/
|
|
74
|
+
export function testModel({
|
|
75
|
+
id = `test-model-${++models}`,
|
|
76
|
+
}: {
|
|
77
|
+
/** The model ID the agent asks for. */
|
|
78
|
+
id?: string
|
|
79
|
+
} = {}): {
|
|
80
|
+
/** The agent's `model`. */
|
|
81
|
+
id: string
|
|
82
|
+
/** Every step the agent asked for, in order. */
|
|
83
|
+
steps: ModelStep[]
|
|
84
|
+
/** The `n`th step (from 0), once the agent asks for it. */
|
|
85
|
+
step(n: number): Promise<ModelStep>
|
|
86
|
+
/** Ends every step still open, and each one asked for from now on. */
|
|
87
|
+
close(): void
|
|
88
|
+
} {
|
|
89
|
+
const steps: ModelStep[] = []
|
|
90
|
+
const arrivals: PromiseWithResolvers<ModelStep>[] = []
|
|
91
|
+
const arrival = (n: number): PromiseWithResolvers<ModelStep> =>
|
|
92
|
+
(arrivals[n] ??= Promise.withResolvers<ModelStep>())
|
|
93
|
+
const ends: (() => void)[] = []
|
|
94
|
+
let closed = false
|
|
95
|
+
const model = new MockLanguageModelV4({
|
|
96
|
+
modelId: id,
|
|
97
|
+
doStream: async (options) => {
|
|
98
|
+
let controller!: ReadableStreamDefaultController<StreamPart>
|
|
99
|
+
let open = true
|
|
100
|
+
const end = (): void => {
|
|
101
|
+
if (!open) return
|
|
102
|
+
open = false
|
|
103
|
+
controller.close()
|
|
104
|
+
}
|
|
105
|
+
const stream = new ReadableStream<StreamPart>({
|
|
106
|
+
start(value) {
|
|
107
|
+
controller = value
|
|
108
|
+
controller.enqueue({ type: 'stream-start', warnings: [] })
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
options.abortSignal?.addEventListener('abort', end, { once: true })
|
|
112
|
+
const step: ModelStep = {
|
|
113
|
+
options,
|
|
114
|
+
write(...parts) {
|
|
115
|
+
for (const part of parts) if (open) controller.enqueue(part)
|
|
116
|
+
},
|
|
117
|
+
finish(reason = 'stop') {
|
|
118
|
+
step.write({
|
|
119
|
+
type: 'finish',
|
|
120
|
+
finishReason: { unified: reason, raw: reason },
|
|
121
|
+
usage,
|
|
122
|
+
})
|
|
123
|
+
end()
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
ends.push(end)
|
|
127
|
+
arrival(steps.length).resolve(step)
|
|
128
|
+
steps.push(step)
|
|
129
|
+
if (closed) step.finish()
|
|
130
|
+
return { stream }
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
vi.stubGlobal('AI_SDK_DEFAULT_PROVIDER', {
|
|
134
|
+
specificationVersion: 'v4',
|
|
135
|
+
languageModel: (requested: string) => {
|
|
136
|
+
if (requested !== id) throw new Error(`no test model '${requested}'`)
|
|
137
|
+
return model
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
return {
|
|
141
|
+
id,
|
|
142
|
+
steps,
|
|
143
|
+
step: (n) => arrival(n).promise,
|
|
144
|
+
close: () => {
|
|
145
|
+
closed = true
|
|
146
|
+
for (const end of ends) end()
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A real HTTP server for the app's route handlers, so the browser's clients
|
|
3
|
+
* reach A2 servers over the actual wire format.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createServer, type ServerResponse } from 'node:http'
|
|
7
|
+
import type { AddressInfo, Socket } from 'node:net'
|
|
8
|
+
import type { A2Operation } from 'experimental-a2/server'
|
|
9
|
+
import { setGlobalOrigin } from 'undici'
|
|
10
|
+
|
|
11
|
+
type Handler = (request: Request) => Response | Promise<Response>
|
|
12
|
+
|
|
13
|
+
export type Served = {
|
|
14
|
+
/** Where the server listens; relative URLs resolve against it until it closes. */
|
|
15
|
+
origin: string
|
|
16
|
+
close(): Promise<void>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const writeResponse = async (
|
|
20
|
+
res: ServerResponse,
|
|
21
|
+
response: Response,
|
|
22
|
+
): Promise<void> => {
|
|
23
|
+
res.writeHead(response.status, Object.fromEntries(response.headers.entries()))
|
|
24
|
+
res.flushHeaders()
|
|
25
|
+
if (!response.body) {
|
|
26
|
+
res.end()
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
const reader = response.body.getReader()
|
|
30
|
+
// A disconnecting client must cancel the web stream, which closes the
|
|
31
|
+
// store subscription behind it.
|
|
32
|
+
res.on('close', () => {
|
|
33
|
+
reader.cancel().catch(() => {})
|
|
34
|
+
})
|
|
35
|
+
try {
|
|
36
|
+
for (;;) {
|
|
37
|
+
// oxlint-disable-next-line no-await-in-loop -- stream pump
|
|
38
|
+
const { done, value } = await reader.read()
|
|
39
|
+
if (done) break
|
|
40
|
+
res.write(value)
|
|
41
|
+
}
|
|
42
|
+
res.end()
|
|
43
|
+
} catch {
|
|
44
|
+
res.destroy()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Serves each route's handler at its path. The page and `fetch` resolve
|
|
50
|
+
* relative URLs, like a module-level client's `api`, against this server
|
|
51
|
+
* until it closes.
|
|
52
|
+
*/
|
|
53
|
+
export async function serve({
|
|
54
|
+
routes,
|
|
55
|
+
}: {
|
|
56
|
+
routes: Readonly<Record<string, Handler>>
|
|
57
|
+
}): Promise<Served> {
|
|
58
|
+
let origin = ''
|
|
59
|
+
const server = createServer((req, res) => {
|
|
60
|
+
void (async () => {
|
|
61
|
+
try {
|
|
62
|
+
const url = new URL(req.url ?? '/', origin)
|
|
63
|
+
const handler = routes[url.pathname]
|
|
64
|
+
if (handler === undefined) {
|
|
65
|
+
res.writeHead(404).end()
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
const chunks: Buffer[] = []
|
|
69
|
+
for await (const chunk of req) chunks.push(chunk as Buffer)
|
|
70
|
+
const request = new Request(url, {
|
|
71
|
+
method: req.method ?? 'GET',
|
|
72
|
+
headers: Object.entries(req.headers).flatMap(([name, value]) =>
|
|
73
|
+
value === undefined
|
|
74
|
+
? []
|
|
75
|
+
: (Array.isArray(value) ? value : [value]).map(
|
|
76
|
+
(item): [string, string] => [name, item],
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
...(chunks.length === 0 ? {} : { body: Buffer.concat(chunks) }),
|
|
80
|
+
})
|
|
81
|
+
await writeResponse(res, await handler(request))
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// A handler's bug answers as one, not as a dropped connection the
|
|
84
|
+
// client would retry.
|
|
85
|
+
if (res.headersSent) res.destroy()
|
|
86
|
+
else res.writeHead(500).end(String(error))
|
|
87
|
+
}
|
|
88
|
+
})()
|
|
89
|
+
})
|
|
90
|
+
// close() must never wait on a request a test parked.
|
|
91
|
+
const sockets = new Set<Socket>()
|
|
92
|
+
server.on('connection', (socket) => {
|
|
93
|
+
sockets.add(socket)
|
|
94
|
+
socket.on('close', () => sockets.delete(socket))
|
|
95
|
+
})
|
|
96
|
+
// The address clients use: on macOS, another process can bind 127.0.0.1 on
|
|
97
|
+
// a port this server holds on every address, and take its connections.
|
|
98
|
+
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve))
|
|
99
|
+
const { port } = server.address() as AddressInfo
|
|
100
|
+
origin = `http://127.0.0.1:${port}`
|
|
101
|
+
setGlobalOrigin(origin)
|
|
102
|
+
// Vitest's jsdom environment exposes its JSDOM, which sets the page's URL.
|
|
103
|
+
;(
|
|
104
|
+
globalThis as { jsdom?: { reconfigure(settings: { url: string }): void } }
|
|
105
|
+
).jsdom?.reconfigure({ url: `${origin}/` })
|
|
106
|
+
return {
|
|
107
|
+
origin,
|
|
108
|
+
close: () =>
|
|
109
|
+
new Promise((resolve) => {
|
|
110
|
+
setGlobalOrigin(undefined)
|
|
111
|
+
for (const socket of sockets) socket.destroy()
|
|
112
|
+
server.close(() => resolve())
|
|
113
|
+
}),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type Match = (operation: A2Operation) => boolean
|
|
118
|
+
|
|
119
|
+
export type Held = {
|
|
120
|
+
/** The parked operation, once it arrives. */
|
|
121
|
+
arrived: Promise<A2Operation>
|
|
122
|
+
allow(): void
|
|
123
|
+
deny(): void
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* An `authorize` for `server.fetch` that records every operation it sees,
|
|
128
|
+
* and can park or deny the next one a test names.
|
|
129
|
+
*/
|
|
130
|
+
export function authorizer(): {
|
|
131
|
+
authorize: (operation: A2Operation) => Promise<boolean>
|
|
132
|
+
/** Every operation so far, in arrival order. */
|
|
133
|
+
operations: A2Operation[]
|
|
134
|
+
/** Parks the next matching operation until the test decides it. */
|
|
135
|
+
hold(match: Match): Held
|
|
136
|
+
/** Denies the next matching operation. */
|
|
137
|
+
deny(match: Match): void
|
|
138
|
+
/** Forgets the rules not yet used, and denies what is still parked. */
|
|
139
|
+
release(): void
|
|
140
|
+
} {
|
|
141
|
+
const operations: A2Operation[] = []
|
|
142
|
+
const rules: {
|
|
143
|
+
match: Match
|
|
144
|
+
decide: (operation: A2Operation) => Promise<boolean>
|
|
145
|
+
}[] = []
|
|
146
|
+
const parked = new Set<Held>()
|
|
147
|
+
return {
|
|
148
|
+
operations,
|
|
149
|
+
authorize: async (operation) => {
|
|
150
|
+
operations.push(operation)
|
|
151
|
+
const at = rules.findIndex((rule) => rule.match(operation))
|
|
152
|
+
return at === -1 || rules.splice(at, 1)[0]!.decide(operation)
|
|
153
|
+
},
|
|
154
|
+
hold(match) {
|
|
155
|
+
const arrival = Promise.withResolvers<A2Operation>()
|
|
156
|
+
const decision = Promise.withResolvers<boolean>()
|
|
157
|
+
const decide = (allowed: boolean): void => {
|
|
158
|
+
parked.delete(held)
|
|
159
|
+
decision.resolve(allowed)
|
|
160
|
+
}
|
|
161
|
+
const held: Held = {
|
|
162
|
+
arrived: arrival.promise,
|
|
163
|
+
allow: () => decide(true),
|
|
164
|
+
deny: () => decide(false),
|
|
165
|
+
}
|
|
166
|
+
parked.add(held)
|
|
167
|
+
rules.push({
|
|
168
|
+
match,
|
|
169
|
+
decide: (operation) => {
|
|
170
|
+
arrival.resolve(operation)
|
|
171
|
+
return decision.promise
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
return held
|
|
175
|
+
},
|
|
176
|
+
deny(match) {
|
|
177
|
+
rules.push({ match, decide: async () => false })
|
|
178
|
+
},
|
|
179
|
+
release() {
|
|
180
|
+
rules.length = 0
|
|
181
|
+
for (const held of parked) held.deny()
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import { defineConfig } from 'vitest/config'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
resolve: {
|
|
6
|
+
tsconfigPaths: true,
|
|
7
|
+
alias: {
|
|
8
|
+
'server-only': fileURLToPath(
|
|
9
|
+
new URL('./test/server-only.ts', import.meta.url),
|
|
10
|
+
),
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
test: {
|
|
14
|
+
// The app's servers run in-process: with no store URL they use A2's
|
|
15
|
+
// memory store, never a database a dev shell points at, and with no
|
|
16
|
+
// queues their work drains in the background of the append. Without
|
|
17
|
+
// credentials, nothing can reach the AI Gateway.
|
|
18
|
+
env: {
|
|
19
|
+
REDIS_URL: '',
|
|
20
|
+
DATABASE_URL: '',
|
|
21
|
+
VERCEL: '',
|
|
22
|
+
A2_QUEUES: '',
|
|
23
|
+
AI_GATEWAY_API_KEY: '',
|
|
24
|
+
VERCEL_OIDC_TOKEN: '',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The a2slack drills (app/a2slack/drills): load, rebuild, and failure runs
|
|
2
|
+
// against a throwaway Redis and Postgres they start themselves. Not part of
|
|
3
|
+
// `pnpm test`; run them with `pnpm drills`.
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { defineConfig } from 'vitest/config'
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
resolve: {
|
|
9
|
+
tsconfigPaths: true,
|
|
10
|
+
alias: {
|
|
11
|
+
'server-only': fileURLToPath(
|
|
12
|
+
new URL('./test/server-only.ts', import.meta.url),
|
|
13
|
+
),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
test: {
|
|
17
|
+
include: ['app/a2slack/drills/*.drill.ts'],
|
|
18
|
+
fileParallelism: false,
|
|
19
|
+
testTimeout: 15 * 60_000,
|
|
20
|
+
hookTimeout: 120_000,
|
|
21
|
+
env: {
|
|
22
|
+
VERCEL: '',
|
|
23
|
+
A2_QUEUES: '',
|
|
24
|
+
AI_GATEWAY_API_KEY: '',
|
|
25
|
+
VERCEL_OIDC_TOKEN: '',
|
|
26
|
+
KV_REST_API_URL: '',
|
|
27
|
+
KV_REST_API_TOKEN: '',
|
|
28
|
+
REDIS_KV_REST_API_URL: '',
|
|
29
|
+
REDIS_KV_REST_API_TOKEN: '',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "experimental-a2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "Syncs your immutable event log, from server to browser: one contract, handlers, derived state, and a live client per session.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"dist",
|
|
22
22
|
"docs",
|
|
23
23
|
"examples",
|
|
24
|
+
"sql",
|
|
24
25
|
"src",
|
|
25
26
|
"AGENTS.md",
|
|
26
27
|
"CHANGELOG.md",
|
|
@@ -112,6 +113,9 @@
|
|
|
112
113
|
},
|
|
113
114
|
"devDependencies": {
|
|
114
115
|
"@electric-sql/pglite": "^0.3.14",
|
|
116
|
+
"experimental-a2-0-20": "npm:experimental-a2@0.20.0",
|
|
117
|
+
"drizzle-kit": "^0.31.11",
|
|
118
|
+
"drizzle-orm": "^0.45.3",
|
|
115
119
|
"@upstash/qstash": "^2.11.3",
|
|
116
120
|
"ai": "^7.0.58",
|
|
117
121
|
"ioredis": "^5.9.0",
|
|
@@ -120,6 +124,7 @@
|
|
|
120
124
|
"@types/ws": "^8.18.1",
|
|
121
125
|
"@vercel/queue": "^0.2.0",
|
|
122
126
|
"pg": "^8.16.0",
|
|
127
|
+
"postgres": "^3.4.9",
|
|
123
128
|
"ws": "^8.21.3",
|
|
124
129
|
"@testing-library/react": "^16.1.0",
|
|
125
130
|
"@types/react": "^19.0.0",
|
|
@@ -144,7 +149,7 @@
|
|
|
144
149
|
"scripts": {
|
|
145
150
|
"build": "tsdown",
|
|
146
151
|
"dev": "tsdown --watch",
|
|
147
|
-
"typecheck": "tsc --noEmit",
|
|
152
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.reference.json",
|
|
148
153
|
"test": "vitest run",
|
|
149
154
|
"test:watch": "vitest"
|
|
150
155
|
}
|