experimental-a2 0.16.0 → 0.17.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/CHANGELOG.md +360 -0
- package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
- package/dist/actor-DBbe5xeE.d.ts.map +1 -0
- package/dist/actor-client.d.ts +2 -5
- package/dist/actor-client.d.ts.map +1 -1
- package/dist/actor-client.js +13 -4
- package/dist/actor-client.js.map +1 -1
- package/dist/actor-react.d.ts +5 -3
- package/dist/actor-react.d.ts.map +1 -1
- package/dist/actor-react.js +20 -10
- package/dist/actor-react.js.map +1 -1
- package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
- package/dist/actor-shared-t7w8xIKP.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +42 -23
- package/dist/actor.js.map +1 -1
- package/dist/{ai-CrLYwNEx.js → ai-UrWfKeuv.js} +966 -541
- package/dist/ai-UrWfKeuv.js.map +1 -0
- package/dist/ai-client.d.ts +27 -0
- package/dist/ai-client.d.ts.map +1 -0
- package/dist/ai-client.js +29 -0
- package/dist/ai-client.js.map +1 -0
- package/dist/ai-react.d.ts +96 -0
- package/dist/ai-react.d.ts.map +1 -0
- package/dist/ai-react.js +142 -0
- package/dist/ai-react.js.map +1 -0
- package/dist/ai-server.browser.js +2 -2
- package/dist/ai-server.browser.js.map +1 -1
- package/dist/ai-server.d.ts +80 -36
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +466 -265
- package/dist/ai-server.js.map +1 -1
- package/dist/{ai-HJ9fHfYI.d.ts → ai-vpGtcaCB.d.ts} +115 -101
- package/dist/ai-vpGtcaCB.d.ts.map +1 -0
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
- package/dist/client-COaiaDub.js.map +1 -0
- package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
- package/dist/client-qRnRUzBS.d.ts.map +1 -0
- package/dist/client.d.ts +4 -2
- package/dist/client.js +1 -1
- package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
- package/dist/contract-BYKASlCV.js.map +1 -0
- package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
- package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
- package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
- package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
- package/dist/internal-CbVvEwad.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/react-BbJ-g6Uu.d.ts +86 -0
- package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
- package/dist/react.d.ts +2 -122
- package/dist/react.js +107 -100
- package/dist/react.js.map +1 -1
- package/dist/reducer-BAXc8JyF.js +87 -0
- package/dist/reducer-BAXc8JyF.js.map +1 -0
- package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
- package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
- package/dist/reducer-runtime-DQwz_J59.js +28 -0
- package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +33 -10
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -0
- package/dist/scheduler-vercel.d.ts +4 -9
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +25 -44
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
- package/dist/server-CGM8WzRG.d.ts.map +1 -0
- package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
- package/dist/server-Rj64fNvX.js.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
- package/dist/session-clear-DGI4jTQ5.js.map +1 -0
- package/dist/session-clear-Dshpuhff.d.ts +12 -0
- package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
- package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
- package/dist/store-Gpkq7WBo.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +182 -69
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
- package/dist/store-polling-4_y8lETm.js.map +1 -0
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +399 -108
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
- package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.d.ts.map +1 -1
- package/dist/store-redis-http.js +293 -11
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
- package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +11 -11
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +166 -32
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-Cx7AAPGq.js +23 -0
- package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
- package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
- package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
- package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
- package/dist/wire-DjfHOvdW.js.map +1 -0
- package/docs/01-quickstart.mdx +2 -2
- package/docs/actors/01-introduction.mdx +4 -0
- package/docs/actors/04-routes.mdx +14 -1
- package/docs/concepts/01-contracts.mdx +95 -0
- package/docs/concepts/04-state.mdx +3 -3
- package/docs/guides/01-timers.mdx +22 -14
- package/docs/guides/02-cancellation.mdx +1 -1
- package/docs/guides/03-react.mdx +268 -328
- package/docs/guides/04-local-first.mdx +32 -73
- package/docs/guides/05-production.mdx +40 -27
- package/docs/guides/06-ai-agents.mdx +680 -580
- package/docs/guides/09-presence.mdx +3 -7
- package/docs/guides/10-transports.mdx +5 -5
- package/docs/index.mdx +10 -18
- package/docs/reference/01-api.mdx +662 -195
- package/docs/reference/02-errors.mdx +2 -1
- package/examples/playground/DEPLOY.md +52 -6
- package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
- package/examples/playground/app/a0/[appId]/page.tsx +48 -0
- package/examples/playground/app/a0/a0.css +818 -0
- package/examples/playground/app/a0/app-list.tsx +211 -0
- package/examples/playground/app/a0/app-view.tsx +55 -0
- package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
- package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
- package/examples/playground/app/a0/apps.test.ts +116 -0
- package/examples/playground/app/a0/apps.ts +87 -0
- package/examples/playground/app/a0/chat.tsx +364 -0
- package/examples/playground/app/a0/columns.tsx +109 -0
- package/examples/playground/app/a0/context-menu.tsx +116 -0
- package/examples/playground/app/a0/cookies.ts +46 -0
- package/examples/playground/app/a0/crash-on-request.ts +72 -0
- package/examples/playground/app/a0/crashes/route.ts +3 -0
- package/examples/playground/app/a0/crashes.test.ts +33 -0
- package/examples/playground/app/a0/crashes.ts +28 -0
- package/examples/playground/app/a0/devtools.tsx +164 -0
- package/examples/playground/app/a0/events/route.ts +6 -0
- package/examples/playground/app/a0/flags.ts +30 -0
- package/examples/playground/app/a0/layout.tsx +38 -0
- package/examples/playground/app/a0/list/route.ts +10 -0
- package/examples/playground/app/a0/model.test.ts +56 -0
- package/examples/playground/app/a0/model.ts +58 -0
- package/examples/playground/app/a0/page.tsx +14 -0
- package/examples/playground/app/a0/preview-pane.tsx +145 -0
- package/examples/playground/app/a0/preview.test.ts +53 -0
- package/examples/playground/app/a0/preview.ts +26 -0
- package/examples/playground/app/a0/sandbox-log.ts +69 -0
- package/examples/playground/app/a0/sandbox.ts +191 -0
- package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
- package/examples/playground/app/a0/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/server.ts +158 -0
- package/examples/playground/app/a0/session.ts +47 -0
- package/examples/playground/app/a0/shell.tsx +150 -0
- package/examples/playground/app/a0/short/events/route.ts +9 -0
- package/examples/playground/app/a0/splitter.tsx +120 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
- package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
- package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
- package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
- package/examples/playground/app/agent/events/route.ts +5 -2
- package/examples/playground/app/agent/ingress.test.ts +98 -0
- package/examples/playground/app/agent/model-input.test.tsx +58 -0
- package/examples/playground/app/agent/model-input.tsx +115 -0
- package/examples/playground/app/agent/model.test.ts +73 -0
- package/examples/playground/app/agent/model.ts +58 -26
- package/examples/playground/app/agent/new-agent-session.tsx +37 -19
- package/examples/playground/app/agent/page.tsx +4 -4
- package/examples/playground/app/agent/server.ts +15 -14
- package/examples/playground/app/agent/session.ts +5 -6
- package/examples/playground/app/agent/telemetry.test.ts +168 -0
- package/examples/playground/app/agent/telemetry.ts +30 -0
- package/examples/playground/app/agent/trace.test.ts +23 -0
- package/examples/playground/app/agent/trace.ts +18 -0
- package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
- package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
- package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
- package/examples/playground/app/canvas/model.ts +53 -53
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
- package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
- package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
- package/examples/playground/app/chat/[chatId]/session.ts +2 -19
- package/examples/playground/app/chat/agent/route.ts +9 -0
- package/examples/playground/app/chat/model.test.ts +46 -119
- package/examples/playground/app/chat/model.ts +82 -128
- package/examples/playground/app/chat/scheduler/route.ts +2 -2
- package/examples/playground/app/chat/server.test.ts +108 -0
- package/examples/playground/app/chat/server.ts +69 -73
- package/examples/playground/app/components/activity-feed.tsx +30 -1
- package/examples/playground/app/components/activity-history.test.tsx +35 -0
- package/examples/playground/app/components/demo-icons.tsx +89 -0
- package/examples/playground/app/components/demos.tsx +33 -0
- package/examples/playground/app/components/fast-link.test.tsx +58 -0
- package/examples/playground/app/components/fast-link.tsx +55 -0
- package/examples/playground/app/components/site-header.tsx +96 -0
- package/examples/playground/app/counter/counter-client.tsx +3 -3
- package/examples/playground/app/counter/model.test.ts +3 -3
- package/examples/playground/app/counter/model.ts +11 -21
- package/examples/playground/app/counter/page.tsx +2 -15
- package/examples/playground/app/counter/session.ts +2 -2
- package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
- package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
- package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
- package/examples/playground/app/documents/[documentId]/session.ts +2 -2
- package/examples/playground/app/documents/model.ts +27 -27
- package/examples/playground/app/globals.css +420 -207
- package/examples/playground/app/initial-state.test.tsx +54 -0
- package/examples/playground/app/layout.tsx +7 -23
- package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
- package/examples/playground/app/orders/[orderId]/session.ts +2 -2
- package/examples/playground/app/orders/model.ts +49 -49
- package/examples/playground/app/page.tsx +13 -102
- package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
- package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
- package/examples/playground/app/recovery/model.ts +30 -30
- package/examples/playground/instrumentation.ts +5 -0
- package/examples/playground/package.json +10 -2
- package/examples/playground/scripts/a0-template.ts +77 -0
- package/examples/playground/vercel.json +18 -0
- package/package.json +3 -1
- package/src/actor-client.ts +16 -4
- package/src/actor-react.ts +25 -11
- package/src/actor-shared.ts +21 -6
- package/src/actor.ts +76 -31
- package/src/ai-agent-actions.ts +219 -0
- package/src/ai-agent.ts +2343 -0
- package/src/ai-app-state.ts +91 -0
- package/src/ai-client-state.ts +39 -2
- package/src/ai-client.ts +62 -0
- package/src/ai-context-schema.ts +25 -5
- package/src/ai-context.ts +90 -40
- package/src/ai-control-server.ts +94 -55
- package/src/ai-control.ts +2 -0
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +382 -58
- package/src/ai-progress-batches.ts +26 -0
- package/src/ai-projector.ts +95 -14
- package/src/ai-react.ts +146 -0
- package/src/ai-server.browser.ts +2 -2
- package/src/ai-server.ts +20 -1951
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +343 -276
- package/src/bootstrap-stream.ts +72 -0
- package/src/bounded-cache.ts +55 -0
- package/src/client-render-session.ts +49 -0
- package/src/client.ts +688 -151
- package/src/contract.ts +92 -42
- package/src/deterministic-id.ts +37 -0
- package/src/equal-json.ts +32 -0
- package/src/errors.ts +13 -1
- package/src/index.ts +5 -1
- package/src/initial-snapshot.ts +39 -0
- package/src/internal.ts +45 -0
- package/src/postgres-notification-scope.ts +14 -3
- package/src/react.ts +171 -206
- package/src/redis-http-reply.ts +256 -0
- package/src/redis-http-stream.ts +73 -0
- package/src/redis-http-subscriptions.ts +13 -5
- package/src/reducer-runtime.ts +52 -0
- package/src/reducer.ts +92 -10
- package/src/scheduler-qstash.ts +49 -20
- package/src/scheduler-task.ts +34 -2
- package/src/scheduler-vercel.ts +32 -84
- package/src/server-fetch.ts +139 -35
- package/src/server.ts +957 -237
- package/src/session-clear.ts +23 -0
- package/src/session-socket.ts +154 -49
- package/src/sse.ts +71 -4
- package/src/store-memory.ts +220 -78
- package/src/store-polling.ts +18 -4
- package/src/store-postgres.ts +461 -114
- package/src/store-redis-core.ts +1260 -434
- package/src/store-redis-http.ts +18 -2
- package/src/store-redis-notify.ts +260 -85
- package/src/store-redis.ts +19 -9
- package/src/store-sqlite.ts +259 -45
- package/src/store.ts +46 -1
- package/src/stream-buffer.ts +43 -0
- package/src/telemetry.ts +13 -1
- package/src/wire.ts +241 -13
- package/dist/actor-ohPC-81x.d.ts.map +0 -1
- package/dist/actor-shared-B5tJfzt-.js.map +0 -1
- package/dist/ai-CrLYwNEx.js.map +0 -1
- package/dist/ai-HJ9fHfYI.d.ts.map +0 -1
- package/dist/client-BYzHjkwU.js.map +0 -1
- package/dist/client-CzyacQpJ.d.ts.map +0 -1
- package/dist/contract-CKRg_E4q.js.map +0 -1
- package/dist/errors-DCk6ch5n.js.map +0 -1
- package/dist/errors-DvhSXnxk.d.ts.map +0 -1
- package/dist/internal-Dq2qYxou.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/reducer-DEMjEY_O.js +0 -29
- package/dist/reducer-DEMjEY_O.js.map +0 -1
- package/dist/reducer-otzHjuJj.d.ts.map +0 -1
- package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
- package/dist/server-BD5ckxZb.js.map +0 -1
- package/dist/server-DbD7IJVK.d.ts.map +0 -1
- package/dist/store-DyZM6fS5.d.ts.map +0 -1
- package/dist/store-polling-CmxUbV93.js.map +0 -1
- package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
- package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
- package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
- package/dist/validate-XKT4FSNn.js +0 -30
- package/dist/validate-XKT4FSNn.js.map +0 -1
- package/dist/wire-BO5wWCb1.js.map +0 -1
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
- package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
- package/examples/playground/app/agent/compaction-settings.ts +0 -49
- package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
- package/examples/playground/app/agent/compaction-timeline.ts +0 -208
package/src/ai-control-server.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { reducer as makeReducer } from './reducer.ts'
|
|
1
2
|
// oxlint-disable no-await-in-loop -- work acknowledgements follow the durable stream.
|
|
2
3
|
import { applyControlChanges, controlCommit } from './ai-control-state.ts'
|
|
3
4
|
import type { ControlChange, ControlCommit } from './ai-control.ts'
|
|
4
5
|
import type { UIMessage } from 'ai'
|
|
5
6
|
import type {
|
|
6
7
|
AIEventDefs,
|
|
7
|
-
AgentDefinition,
|
|
8
8
|
GenerationRequestedPayload,
|
|
9
9
|
MessageInterruptedPayload,
|
|
10
10
|
} from './ai.ts'
|
|
@@ -21,7 +21,12 @@ import {
|
|
|
21
21
|
type WorkFact,
|
|
22
22
|
type WorkReport,
|
|
23
23
|
} from './ai-control.ts'
|
|
24
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
AppendInput,
|
|
26
|
+
Contract,
|
|
27
|
+
ContractEvent,
|
|
28
|
+
EventDefs,
|
|
29
|
+
} from './contract.ts'
|
|
25
30
|
import type { HandlerContext } from './server.ts'
|
|
26
31
|
import type { Reducer } from './reducer.ts'
|
|
27
32
|
import type { AICoordinatorState, CoordinatorCall } from './ai-coordinator.ts'
|
|
@@ -29,6 +34,7 @@ import { validateSync } from './validate.ts'
|
|
|
29
34
|
import type { StandardSchemaV1 } from './standard-schema.ts'
|
|
30
35
|
import { A2Error } from './errors.ts'
|
|
31
36
|
import { aiEventId } from './ai-id.ts'
|
|
37
|
+
import { CONSOLE_TELEMETRY, type A2Telemetry } from './telemetry.ts'
|
|
32
38
|
|
|
33
39
|
export class ControlCancelled extends Error {}
|
|
34
40
|
|
|
@@ -69,22 +75,42 @@ export const createControlRuntime = <
|
|
|
69
75
|
M extends UIMessage,
|
|
70
76
|
D extends AIEventDefs<M> & EventDefs,
|
|
71
77
|
>(options: {
|
|
72
|
-
|
|
78
|
+
contract: Contract<D, Record<never, never>, unknown>
|
|
79
|
+
telemetry?: A2Telemetry
|
|
73
80
|
}): AIControlRuntime<M, D> => {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
const telemetry = options.telemetry ?? CONSOLE_TELEMETRY
|
|
82
|
+
const reducer = makeReducer({
|
|
83
|
+
contract: options.contract,
|
|
84
|
+
name: 'a2.ai.control.v5',
|
|
85
|
+
initialState: initialControlState<M>(),
|
|
86
|
+
}).fold((state, event) => {
|
|
87
|
+
if (event.type === 'ai.control.requested') {
|
|
88
|
+
const command = event.payload as ControlCommand<M>
|
|
89
|
+
if (command.action === 'stop')
|
|
90
|
+
return {
|
|
91
|
+
...state,
|
|
92
|
+
pendingStops: {
|
|
93
|
+
...state.pendingStops,
|
|
94
|
+
[event.id]: command.turnId,
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (event.type === 'ai.control.decided') {
|
|
99
|
+
const receipt = event.payload as ControlReceipt
|
|
100
|
+
if (Object.hasOwn(state.pendingStops, receipt.commandId)) {
|
|
101
|
+
const pendingStops = { ...state.pendingStops }
|
|
102
|
+
delete pendingStops[receipt.commandId]
|
|
103
|
+
return { ...state, pendingStops }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return event.type === 'ai.control.committed'
|
|
107
|
+
? applyControlChanges({
|
|
108
|
+
state,
|
|
109
|
+
changes: (event.payload as ControlCommit).changes,
|
|
110
|
+
})
|
|
111
|
+
: state
|
|
112
|
+
})
|
|
113
|
+
const coordinator = aiCoordinatorReducer(options.contract)
|
|
88
114
|
|
|
89
115
|
const handler = async (
|
|
90
116
|
ctx: HandlerContext<
|
|
@@ -419,7 +445,7 @@ export const createControlRuntime = <
|
|
|
419
445
|
},
|
|
420
446
|
id: await aiEventId(
|
|
421
447
|
'a2.ai.request.retry',
|
|
422
|
-
options.
|
|
448
|
+
options.contract.name,
|
|
423
449
|
ctx.event.sessionId,
|
|
424
450
|
command.request.retryId,
|
|
425
451
|
),
|
|
@@ -649,7 +675,7 @@ export const createControlRuntime = <
|
|
|
649
675
|
payload: { message: item.message, generate: item.generate },
|
|
650
676
|
id: await aiEventId(
|
|
651
677
|
'a2.ai.admitted',
|
|
652
|
-
options.
|
|
678
|
+
options.contract.name,
|
|
653
679
|
ctx.event.sessionId,
|
|
654
680
|
item.id,
|
|
655
681
|
),
|
|
@@ -665,7 +691,7 @@ export const createControlRuntime = <
|
|
|
665
691
|
}
|
|
666
692
|
const requestId = await aiEventId(
|
|
667
693
|
'a2.ai.request.message',
|
|
668
|
-
options.
|
|
694
|
+
options.contract.name,
|
|
669
695
|
ctx.event.sessionId,
|
|
670
696
|
item.id,
|
|
671
697
|
)
|
|
@@ -717,7 +743,7 @@ export const createControlRuntime = <
|
|
|
717
743
|
GenerationRequestedPayload | { turnId: string; version: number }
|
|
718
744
|
const { turnId, version } = 'turnId' in payload ? payload : payload.control!
|
|
719
745
|
for (const event of events) {
|
|
720
|
-
const schema = options.
|
|
746
|
+
const schema = options.contract.events[
|
|
721
747
|
event.type
|
|
722
748
|
] as StandardSchemaV1<unknown>
|
|
723
749
|
const result = validateSync(
|
|
@@ -789,41 +815,54 @@ export const createControlRuntime = <
|
|
|
789
815
|
const input = await report({ ctx, events: lifecycle, settled: false, name })
|
|
790
816
|
const persisted = await ctx.session.append(name, ...progress, input)
|
|
791
817
|
const index = persisted.at(-1)!.index
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
if (
|
|
812
|
-
item.value.type === 'ai.control.decided' &&
|
|
813
|
-
(item.value.payload as ControlReceipt).commandId === input.id
|
|
814
|
-
) {
|
|
815
|
-
if ((item.value.payload as ControlReceipt).outcome === 'rejected')
|
|
816
|
-
throw new ControlCancelled('stale AI work')
|
|
817
|
-
return [
|
|
818
|
-
...persisted.filter((event) => event.type !== 'ai.work.reported'),
|
|
819
|
-
...canonical,
|
|
820
|
-
]
|
|
818
|
+
return telemetry.span(
|
|
819
|
+
'a2.ai.ack',
|
|
820
|
+
{
|
|
821
|
+
'a2.contract': options.contract.name,
|
|
822
|
+
'a2.session_id': ctx.event.sessionId,
|
|
823
|
+
'a2.ai.ack.request_id': ctx.event.id,
|
|
824
|
+
'a2.ai.ack.report_id': input.id!,
|
|
825
|
+
},
|
|
826
|
+
async () => {
|
|
827
|
+
const feed = ctx.session.stream({ startAfter: index })
|
|
828
|
+
const stream = feed[Symbol.asyncIterator]()
|
|
829
|
+
const canonical: ContractEvent<D>[] = []
|
|
830
|
+
const wanted = new Set(lifecycle.map((event) => event.id))
|
|
831
|
+
let rejectAbort!: (error: unknown) => void
|
|
832
|
+
const aborted = new Promise<never>((_, reject) => {
|
|
833
|
+
rejectAbort = reject
|
|
834
|
+
})
|
|
835
|
+
const onAbort = (): void => {
|
|
836
|
+
rejectAbort(ctx.signal.reason)
|
|
821
837
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
838
|
+
ctx.signal.addEventListener('abort', onAbort, { once: true })
|
|
839
|
+
try {
|
|
840
|
+
ctx.signal.throwIfAborted()
|
|
841
|
+
for (;;) {
|
|
842
|
+
const item = await Promise.race([stream.next(), aborted])
|
|
843
|
+
if (item.done)
|
|
844
|
+
throw new Error('AI control acknowledgement stream ended')
|
|
845
|
+
if (wanted.has(item.value.id)) canonical.push(item.value)
|
|
846
|
+
if (
|
|
847
|
+
item.value.type === 'ai.control.decided' &&
|
|
848
|
+
(item.value.payload as ControlReceipt).commandId === input.id
|
|
849
|
+
) {
|
|
850
|
+
if ((item.value.payload as ControlReceipt).outcome === 'rejected')
|
|
851
|
+
throw new ControlCancelled('stale AI work')
|
|
852
|
+
return [
|
|
853
|
+
...persisted.filter(
|
|
854
|
+
(event) => event.type !== 'ai.work.reported',
|
|
855
|
+
),
|
|
856
|
+
...canonical,
|
|
857
|
+
]
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
} finally {
|
|
861
|
+
ctx.signal.removeEventListener('abort', onAbort)
|
|
862
|
+
await stream.return?.()
|
|
863
|
+
}
|
|
864
|
+
},
|
|
865
|
+
)
|
|
827
866
|
}
|
|
828
867
|
|
|
829
868
|
const settled = ({
|
package/src/ai-control.ts
CHANGED
|
@@ -77,6 +77,7 @@ export type ControlWork = {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export type AIControlState<M extends UIMessage = UIMessage> = {
|
|
80
|
+
pendingStops: Record<string, string>
|
|
80
81
|
inbox: { paused: boolean; items: InboxItem<M>[] }
|
|
81
82
|
active:
|
|
82
83
|
| (ActiveTurn<M> & {
|
|
@@ -109,6 +110,7 @@ export type ToolExecutionRequest = {
|
|
|
109
110
|
export const initialControlState = <
|
|
110
111
|
M extends UIMessage,
|
|
111
112
|
>(): AIControlState<M> => ({
|
|
113
|
+
pendingStops: {},
|
|
112
114
|
inbox: { paused: false, items: [] },
|
|
113
115
|
active: null,
|
|
114
116
|
coordinator: { closed: false, queued: [] },
|
package/src/ai-coordinator.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reducer } from './reducer.ts'
|
|
1
2
|
import type { UIMessage } from 'ai'
|
|
2
3
|
import { applyControlChanges } from './ai-control-state.ts'
|
|
3
4
|
import type { ControlCommit, ControlWork } from './ai-control.ts'
|
|
@@ -255,7 +256,8 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
|
|
|
255
256
|
return inputTokens !== undefined &&
|
|
256
257
|
Number.isFinite(inputTokens) &&
|
|
257
258
|
inputTokens >= 0 &&
|
|
258
|
-
estimate !== undefined
|
|
259
|
+
estimate !== undefined &&
|
|
260
|
+
state.response.generation.model !== undefined
|
|
259
261
|
? {
|
|
260
262
|
...uncalibrated,
|
|
261
263
|
calibration: {
|
|
@@ -436,28 +438,27 @@ export const aiCoordinatorReducer = <
|
|
|
436
438
|
M extends UIMessage,
|
|
437
439
|
D extends AIEventDefs<M> & EventDefs,
|
|
438
440
|
>(
|
|
439
|
-
contract: Contract<D>,
|
|
441
|
+
contract: Contract<D, Record<never, never>, unknown>,
|
|
440
442
|
): Reducer<D, AICoordinatorState> =>
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
.
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
)
|
|
443
|
+
reducer({
|
|
444
|
+
contract: contract,
|
|
445
|
+
name: 'a2.ai.coordinator.v6',
|
|
446
|
+
initialState: {
|
|
447
|
+
closed: false,
|
|
448
|
+
queued: [],
|
|
449
|
+
} as AICoordinatorState,
|
|
450
|
+
}).fold((state, event) =>
|
|
451
|
+
event.type === 'ai.control.committed'
|
|
452
|
+
? applyControlChanges({
|
|
453
|
+
state,
|
|
454
|
+
changes: (event.payload as ControlCommit).changes
|
|
455
|
+
.filter((change) => change.path[0] === 'coordinator')
|
|
456
|
+
.map((change) =>
|
|
457
|
+
Object.assign({}, change, { path: change.path.slice(1) }),
|
|
458
|
+
),
|
|
459
|
+
})
|
|
460
|
+
: state,
|
|
461
|
+
)
|
|
461
462
|
|
|
462
463
|
export const continuationReady = (state: AICoordinatorState): boolean => {
|
|
463
464
|
const response = state.response
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { UIMessage } from 'ai'
|
|
2
|
+
import { sameValue } from './equal-json.ts'
|
|
3
|
+
|
|
4
|
+
type FieldUndo = { value?: unknown } | { remove: true } | { length: number }
|
|
5
|
+
type FieldsUndo = Record<string, FieldUndo>
|
|
6
|
+
type PartUndo = { value: UIMessage['parts'][number] } | { fields: FieldsUndo }
|
|
7
|
+
|
|
8
|
+
export type MessageBase = {
|
|
9
|
+
partCount: number
|
|
10
|
+
fields?: FieldsUndo
|
|
11
|
+
parts?: Record<string, PartUndo>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const record = (value: unknown): value is Record<string, unknown> =>
|
|
15
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
16
|
+
|
|
17
|
+
const assertFields = (fields: unknown): void => {
|
|
18
|
+
if (fields === undefined) return
|
|
19
|
+
if (!record(fields)) throw new TypeError('invalid AI message base fields')
|
|
20
|
+
for (const [key, undo] of Object.entries(fields)) {
|
|
21
|
+
if (
|
|
22
|
+
!record(undo) ||
|
|
23
|
+
Object.keys(undo).length > 1 ||
|
|
24
|
+
!(
|
|
25
|
+
Object.keys(undo).length === 0 ||
|
|
26
|
+
'value' in undo ||
|
|
27
|
+
undo['remove'] === true ||
|
|
28
|
+
(key === 'text' &&
|
|
29
|
+
Number.isSafeInteger(undo['length']) &&
|
|
30
|
+
(undo['length'] as number) >= 0)
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
throw new TypeError('invalid AI message base field')
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Checks decoded recovery data; `restoreMessageBase` trusts this shape. */
|
|
38
|
+
export function assertMessageBase(
|
|
39
|
+
value: unknown,
|
|
40
|
+
): asserts value is MessageBase {
|
|
41
|
+
if (
|
|
42
|
+
!record(value) ||
|
|
43
|
+
!Number.isSafeInteger(value['partCount']) ||
|
|
44
|
+
(value['partCount'] as number) < 0 ||
|
|
45
|
+
(value['partCount'] as number) > 0xffffffff - 1 ||
|
|
46
|
+
Object.keys(value).some(
|
|
47
|
+
(key) => !['partCount', 'fields', 'parts'].includes(key),
|
|
48
|
+
) ||
|
|
49
|
+
(value['parts'] !== undefined && !record(value['parts']))
|
|
50
|
+
)
|
|
51
|
+
throw new TypeError('invalid AI message base')
|
|
52
|
+
assertFields(value['fields'])
|
|
53
|
+
const partCount = value['partCount'] as number
|
|
54
|
+
for (const [key, undo] of Object.entries(value['parts'] ?? {})) {
|
|
55
|
+
const index = Number(key)
|
|
56
|
+
if (
|
|
57
|
+
!Number.isSafeInteger(index) ||
|
|
58
|
+
String(index) !== key ||
|
|
59
|
+
index < 0 ||
|
|
60
|
+
index >= partCount ||
|
|
61
|
+
!record(undo) ||
|
|
62
|
+
Object.keys(undo).length !== 1 ||
|
|
63
|
+
!(('value' in undo && record(undo['value'])) || 'fields' in undo)
|
|
64
|
+
)
|
|
65
|
+
throw new TypeError('invalid AI message base part')
|
|
66
|
+
if ('fields' in undo) assertFields(undo['fields'])
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const restoreFields = (
|
|
71
|
+
current: Record<string, unknown>,
|
|
72
|
+
fields: FieldsUndo | undefined,
|
|
73
|
+
): Record<string, unknown> | undefined => {
|
|
74
|
+
if (fields === undefined) return current
|
|
75
|
+
const restored = { ...current }
|
|
76
|
+
for (const [key, undo] of Object.entries(fields)) {
|
|
77
|
+
if ('remove' in undo) delete restored[key]
|
|
78
|
+
else if ('length' in undo) {
|
|
79
|
+
const text = current[key]
|
|
80
|
+
if (typeof text !== 'string' || undo.length > text.length)
|
|
81
|
+
return undefined
|
|
82
|
+
restored[key] = text.slice(0, undo.length)
|
|
83
|
+
} else
|
|
84
|
+
Object.defineProperty(restored, key, {
|
|
85
|
+
value: undo.value,
|
|
86
|
+
enumerable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
configurable: true,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
return restored
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** `undefined` when `base` no longer describes `current`, including a removed message. */
|
|
95
|
+
export const restoreMessageBase = <M extends UIMessage>(options: {
|
|
96
|
+
current: M | undefined
|
|
97
|
+
base: MessageBase | undefined
|
|
98
|
+
}): M | undefined => {
|
|
99
|
+
const { current, base } = options
|
|
100
|
+
if (base === undefined || current === undefined) return undefined
|
|
101
|
+
const parts = current.parts.slice(0, base.partCount)
|
|
102
|
+
for (const [key, undo] of Object.entries(base.parts ?? {})) {
|
|
103
|
+
const index = Number(key)
|
|
104
|
+
if ('value' in undo) {
|
|
105
|
+
parts[index] = undo.value as M['parts'][number]
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
const part = parts[index]
|
|
109
|
+
const restored =
|
|
110
|
+
part === undefined
|
|
111
|
+
? undefined
|
|
112
|
+
: restoreFields(part as unknown as Record<string, unknown>, undo.fields)
|
|
113
|
+
if (!restored) return undefined
|
|
114
|
+
parts[index] = restored as M['parts'][number]
|
|
115
|
+
}
|
|
116
|
+
if (
|
|
117
|
+
parts.length !== base.partCount ||
|
|
118
|
+
Object.keys(parts).length !== parts.length
|
|
119
|
+
)
|
|
120
|
+
return undefined
|
|
121
|
+
const { parts: _parts, ...fields } = current
|
|
122
|
+
const restored = restoreFields(fields, base.fields)
|
|
123
|
+
return restored && ({ ...restored, parts } as M)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const updateFields = (options: {
|
|
127
|
+
before: Record<string, unknown>
|
|
128
|
+
after: Record<string, unknown>
|
|
129
|
+
undo?: FieldsUndo
|
|
130
|
+
}): FieldsUndo | undefined => {
|
|
131
|
+
const { before, after, undo } = options
|
|
132
|
+
const original = restoreFields(before, undo)
|
|
133
|
+
if (!original) return undefined
|
|
134
|
+
return Object.fromEntries(
|
|
135
|
+
[...new Set([...Object.keys(original), ...Object.keys(after)])].flatMap(
|
|
136
|
+
(key) => {
|
|
137
|
+
if (
|
|
138
|
+
Object.hasOwn(original, key) === Object.hasOwn(after, key) &&
|
|
139
|
+
sameValue(original[key], after[key])
|
|
140
|
+
)
|
|
141
|
+
return []
|
|
142
|
+
const value = original[key]
|
|
143
|
+
const next = after[key]
|
|
144
|
+
const change: FieldUndo = !Object.hasOwn(original, key)
|
|
145
|
+
? { remove: true }
|
|
146
|
+
: key === 'text' &&
|
|
147
|
+
typeof value === 'string' &&
|
|
148
|
+
typeof next === 'string' &&
|
|
149
|
+
next.startsWith(value)
|
|
150
|
+
? { length: value.length }
|
|
151
|
+
: value === undefined
|
|
152
|
+
? {}
|
|
153
|
+
: { value }
|
|
154
|
+
return [[key, change]]
|
|
155
|
+
},
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** `undefined` when `before` no longer matches `base`, so the recovery must be dropped. */
|
|
161
|
+
export const updateMessageBase = (options: {
|
|
162
|
+
base: MessageBase
|
|
163
|
+
before: UIMessage
|
|
164
|
+
after: UIMessage
|
|
165
|
+
}): MessageBase | undefined => {
|
|
166
|
+
const { base, before, after } = options
|
|
167
|
+
if (before === after) return base
|
|
168
|
+
const { parts: beforeParts, ...beforeFields } = before
|
|
169
|
+
const { parts: afterParts, ...afterFields } = after
|
|
170
|
+
const fields = updateFields({
|
|
171
|
+
before: beforeFields,
|
|
172
|
+
after: afterFields,
|
|
173
|
+
...(base.fields && { undo: base.fields }),
|
|
174
|
+
})
|
|
175
|
+
if (!fields) return undefined
|
|
176
|
+
const parts: Array<[string, PartUndo]> = []
|
|
177
|
+
for (let index = 0; index < base.partCount; index += 1) {
|
|
178
|
+
const previous = beforeParts[index]
|
|
179
|
+
const current = afterParts[index]
|
|
180
|
+
const undo = base.parts?.[index]
|
|
181
|
+
const original = undo && 'value' in undo ? undo.value : previous
|
|
182
|
+
if (!original) return undefined
|
|
183
|
+
if (previous === current && undo === undefined) continue
|
|
184
|
+
const originalFields = original as unknown as Record<string, unknown>
|
|
185
|
+
const undoFields = undo && 'fields' in undo ? undo.fields : undefined
|
|
186
|
+
if (!current) {
|
|
187
|
+
const value = restoreFields(originalFields, undoFields)
|
|
188
|
+
if (!value) return undefined
|
|
189
|
+
parts.push([
|
|
190
|
+
String(index),
|
|
191
|
+
{ value: value as UIMessage['parts'][number] },
|
|
192
|
+
])
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
const changed = updateFields({
|
|
196
|
+
before: originalFields,
|
|
197
|
+
after: current as unknown as Record<string, unknown>,
|
|
198
|
+
...(undoFields && { undo: undoFields }),
|
|
199
|
+
})
|
|
200
|
+
if (!changed) return undefined
|
|
201
|
+
if (Object.keys(changed).length > 0)
|
|
202
|
+
parts.push([String(index), { fields: changed }])
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
partCount: base.partCount,
|
|
206
|
+
...(Object.keys(fields).length > 0 && { fields }),
|
|
207
|
+
...(parts.length > 0 && { parts: Object.fromEntries(parts) }),
|
|
208
|
+
}
|
|
209
|
+
}
|