experimental-a2 0.16.1 → 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 +352 -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-DgOBltJ_.js → ai-UrWfKeuv.js} +621 -216
- 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 +432 -258
- package/dist/ai-server.js.map +1 -1
- package/dist/{ai-CrEf6p_W.d.ts → ai-vpGtcaCB.d.ts} +114 -100
- 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 +671 -581
- 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 +656 -194
- 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 +6 -3
- 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 +92 -72
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +262 -64
- 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 -1964
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +339 -155
- 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-CrEf6p_W.d.ts.map +0 -1
- package/dist/ai-DgOBltJ_.js.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,41 +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
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (event.type === 'ai.control.decided') {
|
|
92
|
-
const receipt = event.payload as ControlReceipt
|
|
93
|
-
if (Object.hasOwn(state.pendingStops, receipt.commandId)) {
|
|
94
|
-
const pendingStops = { ...state.pendingStops }
|
|
95
|
-
delete pendingStops[receipt.commandId]
|
|
96
|
-
return { ...state, pendingStops }
|
|
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
|
+
},
|
|
97
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 }
|
|
98
104
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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)
|
|
107
114
|
|
|
108
115
|
const handler = async (
|
|
109
116
|
ctx: HandlerContext<
|
|
@@ -438,7 +445,7 @@ export const createControlRuntime = <
|
|
|
438
445
|
},
|
|
439
446
|
id: await aiEventId(
|
|
440
447
|
'a2.ai.request.retry',
|
|
441
|
-
options.
|
|
448
|
+
options.contract.name,
|
|
442
449
|
ctx.event.sessionId,
|
|
443
450
|
command.request.retryId,
|
|
444
451
|
),
|
|
@@ -668,7 +675,7 @@ export const createControlRuntime = <
|
|
|
668
675
|
payload: { message: item.message, generate: item.generate },
|
|
669
676
|
id: await aiEventId(
|
|
670
677
|
'a2.ai.admitted',
|
|
671
|
-
options.
|
|
678
|
+
options.contract.name,
|
|
672
679
|
ctx.event.sessionId,
|
|
673
680
|
item.id,
|
|
674
681
|
),
|
|
@@ -684,7 +691,7 @@ export const createControlRuntime = <
|
|
|
684
691
|
}
|
|
685
692
|
const requestId = await aiEventId(
|
|
686
693
|
'a2.ai.request.message',
|
|
687
|
-
options.
|
|
694
|
+
options.contract.name,
|
|
688
695
|
ctx.event.sessionId,
|
|
689
696
|
item.id,
|
|
690
697
|
)
|
|
@@ -736,7 +743,7 @@ export const createControlRuntime = <
|
|
|
736
743
|
GenerationRequestedPayload | { turnId: string; version: number }
|
|
737
744
|
const { turnId, version } = 'turnId' in payload ? payload : payload.control!
|
|
738
745
|
for (const event of events) {
|
|
739
|
-
const schema = options.
|
|
746
|
+
const schema = options.contract.events[
|
|
740
747
|
event.type
|
|
741
748
|
] as StandardSchemaV1<unknown>
|
|
742
749
|
const result = validateSync(
|
|
@@ -808,41 +815,54 @@ export const createControlRuntime = <
|
|
|
808
815
|
const input = await report({ ctx, events: lifecycle, settled: false, name })
|
|
809
816
|
const persisted = await ctx.session.append(name, ...progress, input)
|
|
810
817
|
const index = persisted.at(-1)!.index
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if (
|
|
831
|
-
item.value.type === 'ai.control.decided' &&
|
|
832
|
-
(item.value.payload as ControlReceipt).commandId === input.id
|
|
833
|
-
) {
|
|
834
|
-
if ((item.value.payload as ControlReceipt).outcome === 'rejected')
|
|
835
|
-
throw new ControlCancelled('stale AI work')
|
|
836
|
-
return [
|
|
837
|
-
...persisted.filter((event) => event.type !== 'ai.work.reported'),
|
|
838
|
-
...canonical,
|
|
839
|
-
]
|
|
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)
|
|
840
837
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
+
)
|
|
846
866
|
}
|
|
847
867
|
|
|
848
868
|
const settled = ({
|
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
|
+
}
|