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
|
@@ -13,19 +13,21 @@ import {
|
|
|
13
13
|
useRef,
|
|
14
14
|
useState,
|
|
15
15
|
} from 'react'
|
|
16
|
-
import {
|
|
16
|
+
import type { InitialSnapshot } from 'experimental-a2'
|
|
17
|
+
import { inputs, type AIState } from 'experimental-a2/ai'
|
|
18
|
+
import { useAgent } from 'experimental-a2/ai/react'
|
|
17
19
|
import { ActivityFeed } from '@/app/components/activity-feed'
|
|
18
20
|
import { ConnectionPill } from '@/app/components/connection-pill'
|
|
19
|
-
import {
|
|
20
|
-
AgentStreamDrawer,
|
|
21
|
-
type AgentStreamStatus,
|
|
22
|
-
} from './agent-stream-drawer'
|
|
21
|
+
import { AgentStreamDrawer } from './agent-stream-drawer'
|
|
23
22
|
import {
|
|
24
23
|
buildChatTranscript,
|
|
24
|
+
agentRetry,
|
|
25
|
+
agentStreamStatus,
|
|
25
26
|
selectAgentStream,
|
|
26
27
|
type AgentMessageMetadata,
|
|
27
28
|
} from './chat-view'
|
|
28
29
|
import { insertComposerText, isComposerTypingKey } from './composer'
|
|
30
|
+
import { chatAgent, type ChatAgentMessage } from '../model'
|
|
29
31
|
import { useSession } from './session'
|
|
30
32
|
|
|
31
33
|
/** Max one durable presence ping per author in this window. */
|
|
@@ -180,8 +182,22 @@ const hasImportantFocus = (element: EventTarget | null): boolean =>
|
|
|
180
182
|
(element.matches(IMPORTANT_FOCUS_SELECTOR) ||
|
|
181
183
|
element.closest('dialog[open]') !== null)
|
|
182
184
|
|
|
183
|
-
export function ChatClient({
|
|
184
|
-
|
|
185
|
+
export function ChatClient({
|
|
186
|
+
chatId,
|
|
187
|
+
agentInitial,
|
|
188
|
+
}: {
|
|
189
|
+
chatId: string
|
|
190
|
+
agentInitial: InitialSnapshot<AIState<ChatAgentMessage>>
|
|
191
|
+
}): ReactNode {
|
|
192
|
+
const { state, push, events, index, connection, history, loadHistory } =
|
|
193
|
+
useSession()
|
|
194
|
+
const agent = useAgent({
|
|
195
|
+
api: '/chat/agent',
|
|
196
|
+
id: chatId,
|
|
197
|
+
initial: agentInitial,
|
|
198
|
+
contract: chatAgent,
|
|
199
|
+
})
|
|
200
|
+
const ai = agent.state
|
|
185
201
|
|
|
186
202
|
const [name, setName] = useState('')
|
|
187
203
|
const [draft, setDraft] = useState('')
|
|
@@ -277,14 +293,14 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
|
|
|
277
293
|
.filter(([, at]) => now - Date.parse(at) < TYPING_TTL_MS)
|
|
278
294
|
.map(([author]) => author)
|
|
279
295
|
|
|
280
|
-
const runningTool =
|
|
296
|
+
const runningTool = ai.tools.find((tool) => tool.status === 'running')
|
|
281
297
|
const activeResponseId =
|
|
282
|
-
|
|
283
|
-
|
|
298
|
+
ai.activeGeneration?.responseMessageId ??
|
|
299
|
+
ai.activeResponseMessageId ??
|
|
284
300
|
runningTool?.messageId ??
|
|
285
301
|
null
|
|
286
302
|
const activeMessage = activeResponseId
|
|
287
|
-
?
|
|
303
|
+
? ai.messages.find((message) => message.id === activeResponseId)
|
|
288
304
|
: undefined
|
|
289
305
|
const writing =
|
|
290
306
|
activeMessage?.parts.some(
|
|
@@ -294,11 +310,11 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
|
|
|
294
310
|
) === true ||
|
|
295
311
|
(runningTool?.toolName === 'post' && runningTool.status === 'running')
|
|
296
312
|
const agentActivity =
|
|
297
|
-
|
|
313
|
+
ai.status === 'generating' ? (writing ? 'writing' : 'thinking') : null
|
|
298
314
|
const presence = presenceLine(typingAuthors, agentActivity)
|
|
299
315
|
|
|
300
316
|
const aiMessages = new Map(
|
|
301
|
-
|
|
317
|
+
ai.messages.map((message) => [message.id, message]),
|
|
302
318
|
)
|
|
303
319
|
const lastPostByResponse = new Map<string, (typeof state.posts)[number]>()
|
|
304
320
|
for (const post of state.posts) {
|
|
@@ -309,7 +325,7 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
|
|
|
309
325
|
}
|
|
310
326
|
const transcript = buildChatTranscript(state.messages, state.posts)
|
|
311
327
|
const selectedAgentStream = openResponseId
|
|
312
|
-
? selectAgentStream(
|
|
328
|
+
? selectAgentStream(ai.messages, openResponseId)
|
|
313
329
|
: null
|
|
314
330
|
const selectedStreamSummary = usageLabel(
|
|
315
331
|
selectedAgentStream?.metadata ?? null,
|
|
@@ -317,32 +333,12 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
|
|
|
317
333
|
const selectedStreamLive =
|
|
318
334
|
openResponseId !== null &&
|
|
319
335
|
openResponseId === activeResponseId &&
|
|
320
|
-
|
|
321
|
-
const selectedStreamStatus = (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
event.type === 'ai.message.interrupted' &&
|
|
327
|
-
event.payload.messageId === openResponseId
|
|
328
|
-
) {
|
|
329
|
-
return 'interrupted'
|
|
330
|
-
}
|
|
331
|
-
if (
|
|
332
|
-
event.type === 'ai.generation.failed' &&
|
|
333
|
-
event.payload.responseMessageId === openResponseId
|
|
334
|
-
) {
|
|
335
|
-
return 'failed'
|
|
336
|
-
}
|
|
337
|
-
if (
|
|
338
|
-
event.type === 'ai.message.completed' &&
|
|
339
|
-
event.payload.messageId === openResponseId
|
|
340
|
-
) {
|
|
341
|
-
return 'completed'
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return 'completed'
|
|
345
|
-
})() satisfies AgentStreamStatus
|
|
336
|
+
ai.status === 'generating'
|
|
337
|
+
const selectedStreamStatus = agentStreamStatus({
|
|
338
|
+
state: ai,
|
|
339
|
+
responseMessageId: openResponseId,
|
|
340
|
+
streaming: selectedStreamLive,
|
|
341
|
+
})
|
|
346
342
|
|
|
347
343
|
const participants = useMemo(() => {
|
|
348
344
|
const seen = new Set<string>(['agent'])
|
|
@@ -358,26 +354,11 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
|
|
|
358
354
|
)
|
|
359
355
|
: []
|
|
360
356
|
|
|
361
|
-
const totalTokens =
|
|
357
|
+
const totalTokens = ai.usage.reduce(
|
|
362
358
|
(total, entry) => total + (entry.usage.totalTokens ?? 0),
|
|
363
359
|
0,
|
|
364
360
|
)
|
|
365
|
-
const
|
|
366
|
-
state.ai.status === 'failed'
|
|
367
|
-
? events.findLast(
|
|
368
|
-
(
|
|
369
|
-
event,
|
|
370
|
-
): event is Extract<
|
|
371
|
-
(typeof events)[number],
|
|
372
|
-
{ type: 'ai.generation.failed' }
|
|
373
|
-
> =>
|
|
374
|
-
event.type === 'ai.generation.failed' &&
|
|
375
|
-
event.payload.superseded !== true,
|
|
376
|
-
)
|
|
377
|
-
: undefined
|
|
378
|
-
const failedRootMessageId = failedGeneration
|
|
379
|
-
? state.ai.active?.input.id
|
|
380
|
-
: undefined
|
|
361
|
+
const retry = agentRetry(ai)
|
|
381
362
|
|
|
382
363
|
const sendTypingPing = useCallback((): void => {
|
|
383
364
|
if (!name) return
|
|
@@ -535,29 +516,23 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
|
|
|
535
516
|
}
|
|
536
517
|
|
|
537
518
|
const stopAgent = async (): Promise<void> => {
|
|
538
|
-
if (!
|
|
519
|
+
if (!ai.active) return
|
|
539
520
|
setError(null)
|
|
540
521
|
try {
|
|
541
|
-
await
|
|
542
|
-
...inputs.stop({
|
|
543
|
-
turnId: state.ai.active.turnId,
|
|
544
|
-
lastSeenIndex: index,
|
|
545
|
-
}),
|
|
546
|
-
)
|
|
522
|
+
await agent.stop()
|
|
547
523
|
} catch (err) {
|
|
548
524
|
setError(err instanceof Error ? err.message : String(err))
|
|
549
525
|
}
|
|
550
526
|
}
|
|
551
527
|
|
|
552
528
|
const retryAgent = async (): Promise<void> => {
|
|
553
|
-
if (!
|
|
529
|
+
if (!retry) return
|
|
554
530
|
setError(null)
|
|
555
531
|
setRetrying(true)
|
|
556
532
|
try {
|
|
557
|
-
await push(
|
|
533
|
+
await agent.push(
|
|
558
534
|
...inputs.retry({
|
|
559
|
-
|
|
560
|
-
responseMessageId: failedGeneration.payload.responseMessageId,
|
|
535
|
+
...retry,
|
|
561
536
|
retryId: crypto.randomUUID(),
|
|
562
537
|
}),
|
|
563
538
|
)
|
|
@@ -606,12 +581,12 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
|
|
|
606
581
|
{state.messages.length}{' '}
|
|
607
582
|
{state.messages.length === 1 ? 'message' : 'messages'}
|
|
608
583
|
</span>
|
|
609
|
-
{
|
|
584
|
+
{ai.usage.length > 0 ? (
|
|
610
585
|
<span className="badge">
|
|
611
586
|
{totalTokens.toLocaleString()} AI tokens
|
|
612
587
|
</span>
|
|
613
588
|
) : null}
|
|
614
|
-
{
|
|
589
|
+
{ai.status === 'failed' ? (
|
|
615
590
|
<span className="badge cut">agent paused</span>
|
|
616
591
|
) : null}
|
|
617
592
|
</div>
|
|
@@ -730,18 +705,16 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
|
|
|
730
705
|
</div>
|
|
731
706
|
|
|
732
707
|
<div className="chat-composer-dock" ref={composerDockRef}>
|
|
733
|
-
{
|
|
708
|
+
{ai.status === 'failed' ? (
|
|
734
709
|
<div className="chat-agent-error" role="alert">
|
|
735
710
|
<p>
|
|
736
711
|
<strong>Agent paused.</strong>{' '}
|
|
737
|
-
{
|
|
712
|
+
{ai.error ?? 'The model step failed.'}
|
|
738
713
|
</p>
|
|
739
714
|
<button
|
|
740
715
|
type="button"
|
|
741
716
|
className="secondary compact"
|
|
742
|
-
disabled={
|
|
743
|
-
!failedGeneration || !failedRootMessageId || retrying
|
|
744
|
-
}
|
|
717
|
+
disabled={!retry || retrying}
|
|
745
718
|
onClick={() => void retryAgent()}
|
|
746
719
|
>
|
|
747
720
|
{retrying ? 'Retrying…' : 'Retry agent'}
|
|
@@ -888,10 +861,15 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
|
|
|
888
861
|
onClick={() => setSidebarOpen((open) => !open)}
|
|
889
862
|
>
|
|
890
863
|
<span>Room details</span>
|
|
891
|
-
<span className="badge">{events.length} events</span>
|
|
864
|
+
<span className="badge">{events.length} loaded events</span>
|
|
892
865
|
</button>
|
|
893
866
|
<div className="chat-sidebar-body" id="chat-sidebar-body">
|
|
894
|
-
<ActivityFeed
|
|
867
|
+
<ActivityFeed
|
|
868
|
+
events={events}
|
|
869
|
+
index={index}
|
|
870
|
+
history={history}
|
|
871
|
+
loadHistory={loadHistory}
|
|
872
|
+
/>
|
|
895
873
|
</div>
|
|
896
874
|
</aside>
|
|
897
875
|
</div>
|
|
@@ -1,9 +1,43 @@
|
|
|
1
|
+
import type { AIState, TranscriptMessage } from 'experimental-a2/ai'
|
|
1
2
|
import type {
|
|
2
3
|
AgentPost,
|
|
3
4
|
ChatAgentMessage,
|
|
4
5
|
ChatMessage,
|
|
5
6
|
ChatMessageMetadata,
|
|
6
7
|
} from '../model'
|
|
8
|
+
import type { AgentStreamStatus } from './agent-stream-drawer'
|
|
9
|
+
|
|
10
|
+
export function agentStreamStatus({
|
|
11
|
+
state,
|
|
12
|
+
responseMessageId,
|
|
13
|
+
streaming,
|
|
14
|
+
}: {
|
|
15
|
+
state: AIState<ChatAgentMessage>
|
|
16
|
+
responseMessageId: string | null
|
|
17
|
+
streaming: boolean
|
|
18
|
+
}): AgentStreamStatus {
|
|
19
|
+
if (streaming) return 'streaming'
|
|
20
|
+
const owner =
|
|
21
|
+
responseMessageId === null
|
|
22
|
+
? undefined
|
|
23
|
+
: state.responseOwners[responseMessageId]
|
|
24
|
+
const terminal = owner && state.terminalGenerations?.[owner.generationId]
|
|
25
|
+
return terminal === 'failed' || terminal === 'interrupted'
|
|
26
|
+
? terminal
|
|
27
|
+
: 'completed'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function agentRetry(state: AIState<ChatAgentMessage>): {
|
|
31
|
+
messageId: string
|
|
32
|
+
responseMessageId: string
|
|
33
|
+
} | null {
|
|
34
|
+
if (state.status !== 'failed' || state.active === null) return null
|
|
35
|
+
const responseMessageId =
|
|
36
|
+
state.activeResponseMessageId ?? state.activeProjection?.responseMessageId
|
|
37
|
+
return responseMessageId
|
|
38
|
+
? { messageId: state.active.input.id, responseMessageId }
|
|
39
|
+
: null
|
|
40
|
+
}
|
|
7
41
|
|
|
8
42
|
const MESSAGE_BURST_MS = 60_000
|
|
9
43
|
|
|
@@ -80,13 +114,13 @@ export type AgentMessageMetadata = Extract<
|
|
|
80
114
|
>
|
|
81
115
|
|
|
82
116
|
export type AgentStreamSelection = {
|
|
83
|
-
message: ChatAgentMessage
|
|
84
|
-
parts: ChatAgentMessage['parts']
|
|
117
|
+
message: TranscriptMessage<ChatAgentMessage>
|
|
118
|
+
parts: TranscriptMessage<ChatAgentMessage>['parts']
|
|
85
119
|
metadata: AgentMessageMetadata | null
|
|
86
120
|
}
|
|
87
121
|
|
|
88
122
|
export const selectAgentStream = (
|
|
89
|
-
messages: ChatAgentMessage[],
|
|
123
|
+
messages: TranscriptMessage<ChatAgentMessage>[],
|
|
90
124
|
responseMessageId: string,
|
|
91
125
|
): AgentStreamSelection | null => {
|
|
92
126
|
const message = messages.find(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import { chatServer } from '../server'
|
|
2
|
+
import { chatAgentServer, chatServer } from '../server'
|
|
4
3
|
import { SessionProvider } from './session'
|
|
5
4
|
import { ChatClient } from './chat-client'
|
|
6
5
|
|
|
@@ -10,21 +9,14 @@ export default async function ChatSessionPage({
|
|
|
10
9
|
params: Promise<{ chatId: string }>
|
|
11
10
|
}): Promise<ReactNode> {
|
|
12
11
|
const { chatId } = await params
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
)
|
|
12
|
+
const [initial, agentInitial] = await Promise.all([
|
|
13
|
+
chatServer.session(chatId).state(),
|
|
14
|
+
chatAgentServer.session(chatId).state(),
|
|
15
|
+
])
|
|
18
16
|
|
|
19
17
|
return (
|
|
20
|
-
<SessionProvider
|
|
21
|
-
|
|
22
|
-
api="/chat/events"
|
|
23
|
-
initialState={state}
|
|
24
|
-
initialIndex={index}
|
|
25
|
-
initialEvents={events}
|
|
26
|
-
>
|
|
27
|
-
<ChatClient chatId={chatId} />
|
|
18
|
+
<SessionProvider sessionId={chatId} api="/chat/events" initial={initial}>
|
|
19
|
+
<ChatClient chatId={chatId} agentInitial={agentInitial} />
|
|
28
20
|
</SessionProvider>
|
|
29
21
|
)
|
|
30
22
|
}
|
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import { createReact } from 'experimental-a2/react'
|
|
3
|
-
import {
|
|
4
|
-
import { useMemo } from 'react'
|
|
3
|
+
import { chat } from '../model'
|
|
5
4
|
|
|
6
|
-
const { SessionProvider, useSession
|
|
7
|
-
reducer: chatReducer,
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
export { SessionProvider }
|
|
11
|
-
|
|
12
|
-
type ChatSession = Omit<ReturnType<typeof useStoredSession>, 'state'> & {
|
|
13
|
-
state: ChatViewState
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function useSession(): ChatSession {
|
|
17
|
-
const session = useStoredSession()
|
|
18
|
-
return useMemo(
|
|
19
|
-
() => ({ ...session, state: viewChatState(session.state) }),
|
|
20
|
-
[session],
|
|
21
|
-
)
|
|
22
|
-
}
|
|
5
|
+
export const { SessionProvider, useSession } = createReact({ contract: chat })
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { authorizeChatAgentOperation, chatAgentServer } from '../server'
|
|
2
|
+
|
|
3
|
+
export const GET = chatAgentServer.fetch
|
|
4
|
+
|
|
5
|
+
export function POST(request: Request): Promise<Response> {
|
|
6
|
+
return chatAgentServer.fetch(request, {
|
|
7
|
+
authorize: authorizeChatAgentOperation,
|
|
8
|
+
})
|
|
9
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ContractEvent } from 'experimental-a2'
|
|
2
|
-
import {
|
|
3
|
-
import { chat, chatReducer,
|
|
2
|
+
import { expect, it } from 'vitest'
|
|
3
|
+
import { chat, chatReducer, type ChatState } from './model'
|
|
4
4
|
|
|
5
5
|
type ChatEvents = typeof chat.events
|
|
6
6
|
type ChatEvent<
|
|
@@ -21,31 +21,10 @@ const event = <K extends keyof ChatEvents & string>(
|
|
|
21
21
|
createdAt: new Date(index * 1_000),
|
|
22
22
|
}) as ChatEvent<K>
|
|
23
23
|
|
|
24
|
-
const generation = {
|
|
25
|
-
requestId: 'request-1',
|
|
26
|
-
messageId: 'participant-1',
|
|
27
|
-
generationId: 'generation-1',
|
|
28
|
-
responseMessageId: 'assistant-1',
|
|
29
|
-
attempt: 1,
|
|
30
|
-
model: 'test/model',
|
|
31
|
-
} satisfies ChatEvent<'ai.generation.started'>['payload']
|
|
32
|
-
|
|
33
|
-
const setup = (): ChatEvent[] => [event(1, 'ai.generation.started', generation)]
|
|
34
|
-
|
|
35
|
-
const toolCalled = (index: number, toolCallId: string, message: string) =>
|
|
36
|
-
event(index, 'ai.tool.called', {
|
|
37
|
-
requestId: generation.requestId,
|
|
38
|
-
messageId: generation.responseMessageId,
|
|
39
|
-
generationId: generation.generationId,
|
|
40
|
-
toolCallId,
|
|
41
|
-
toolName: 'post',
|
|
42
|
-
input: { message },
|
|
43
|
-
})
|
|
44
|
-
|
|
45
24
|
const messagePosted = (index: number, toolCallId: string, message: string) =>
|
|
46
25
|
event(index, 'agent.message.posted', {
|
|
47
|
-
generationId: generation
|
|
48
|
-
responseMessageId:
|
|
26
|
+
generationId: 'generation-1',
|
|
27
|
+
responseMessageId: 'assistant-1',
|
|
49
28
|
toolCallId,
|
|
50
29
|
message,
|
|
51
30
|
})
|
|
@@ -56,100 +35,48 @@ const fold = (...events: ChatEvent[]): ChatState =>
|
|
|
56
35
|
chatReducer.initialState,
|
|
57
36
|
)
|
|
58
37
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const state = fold(
|
|
94
|
-
...setup(),
|
|
95
|
-
firstCall,
|
|
96
|
-
secondCall,
|
|
97
|
-
messagePosted(4, 'call-1', 'First post'),
|
|
98
|
-
messagePosted(5, 'call-2', 'Second post'),
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
expect(
|
|
102
|
-
state.posts.map(({ toolCallId, index, text }) => ({
|
|
103
|
-
toolCallId,
|
|
104
|
-
index,
|
|
105
|
-
text,
|
|
106
|
-
})),
|
|
107
|
-
).toEqual([
|
|
108
|
-
{ toolCallId: 'call-1', index: 4, text: 'First post' },
|
|
109
|
-
{ toolCallId: 'call-2', index: 5, text: 'Second post' },
|
|
110
|
-
])
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('rewinds posts past an interruption cutpoint and ignores late appends', () => {
|
|
114
|
-
const firstCall = toolCalled(2, 'call-1', 'Visible before stop')
|
|
115
|
-
const secondCall = toolCalled(4, 'call-2', 'Not visible at stop')
|
|
116
|
-
let state = fold(
|
|
117
|
-
...setup(),
|
|
118
|
-
firstCall,
|
|
119
|
-
messagePosted(3, 'call-1', 'Visible before stop'),
|
|
120
|
-
secondCall,
|
|
121
|
-
messagePosted(5, 'call-2', 'Not visible at stop'),
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
expect(state.posts.map((post) => post.toolCallId)).toEqual([
|
|
125
|
-
'call-1',
|
|
126
|
-
'call-2',
|
|
127
|
-
])
|
|
128
|
-
|
|
129
|
-
state = chatReducer.fold(
|
|
130
|
-
state,
|
|
131
|
-
event(6, 'ai.message.interrupted', {
|
|
132
|
-
messageId: generation.responseMessageId,
|
|
133
|
-
generationId: generation.generationId,
|
|
134
|
-
lastSeenIndex: 3,
|
|
135
|
-
}),
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
expect(state.posts.map((post) => post.toolCallId)).toEqual(['call-1'])
|
|
139
|
-
expect(viewChatState(state).ai.activeGeneration).toBeNull()
|
|
140
|
-
|
|
141
|
-
state = chatReducer.fold(
|
|
142
|
-
state,
|
|
143
|
-
toolCalled(7, 'call-3', 'Called after stop'),
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
expect(viewChatState(state).ai.activeGeneration).toBeNull()
|
|
147
|
-
|
|
148
|
-
state = chatReducer.fold(
|
|
149
|
-
state,
|
|
150
|
-
messagePosted(8, 'call-3', 'Late after stop'),
|
|
151
|
-
)
|
|
38
|
+
it("orders the agent's posts among everyone's messages by log index", () => {
|
|
39
|
+
const state = fold(
|
|
40
|
+
event(1, 'typed', { author: 'alice' }),
|
|
41
|
+
event(2, 'message.sent', { author: 'alice', text: '@agent hi' }),
|
|
42
|
+
messagePosted(3, 'call-1', 'Hello'),
|
|
43
|
+
event(4, 'message.sent', { author: 'bob', text: 'mid-reply' }),
|
|
44
|
+
messagePosted(5, 'call-2', 'And more'),
|
|
45
|
+
)
|
|
46
|
+
expect(state.lastTypingAt).toEqual({})
|
|
47
|
+
expect(
|
|
48
|
+
state.messages.map(({ index, author }) => ({ index, author })),
|
|
49
|
+
).toEqual([
|
|
50
|
+
{ index: 2, author: 'alice' },
|
|
51
|
+
{ index: 4, author: 'bob' },
|
|
52
|
+
])
|
|
53
|
+
expect(state.posts).toEqual([
|
|
54
|
+
{
|
|
55
|
+
generationId: 'generation-1',
|
|
56
|
+
responseMessageId: 'assistant-1',
|
|
57
|
+
toolCallId: 'call-1',
|
|
58
|
+
index: 3,
|
|
59
|
+
text: 'Hello',
|
|
60
|
+
at: new Date(3_000).toISOString(),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
generationId: 'generation-1',
|
|
64
|
+
responseMessageId: 'assistant-1',
|
|
65
|
+
toolCallId: 'call-2',
|
|
66
|
+
index: 5,
|
|
67
|
+
text: 'And more',
|
|
68
|
+
at: new Date(5_000).toISOString(),
|
|
69
|
+
},
|
|
70
|
+
])
|
|
71
|
+
})
|
|
152
72
|
|
|
153
|
-
|
|
73
|
+
it('records who changed ambient mode', () => {
|
|
74
|
+
const state = fold(
|
|
75
|
+
event(1, 'agent.ambient.changed', { enabled: true, author: 'alice' }),
|
|
76
|
+
)
|
|
77
|
+
expect(state.ambient).toEqual({
|
|
78
|
+
enabled: true,
|
|
79
|
+
changedBy: 'alice',
|
|
80
|
+
changedAt: new Date(1_000).toISOString(),
|
|
154
81
|
})
|
|
155
82
|
})
|