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
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
|
-
import { demoAgent } from '../model'
|
|
3
2
|
import { agentServer } from '../server'
|
|
4
|
-
import { SessionProvider } from '../session'
|
|
5
3
|
import { AgentClient } from './agent-client'
|
|
6
4
|
|
|
7
5
|
export default async function AgentSessionPage({
|
|
@@ -11,19 +9,7 @@ export default async function AgentSessionPage({
|
|
|
11
9
|
}): Promise<ReactNode> {
|
|
12
10
|
const { agentId } = await params
|
|
13
11
|
const session = agentServer.session(agentId)
|
|
14
|
-
const
|
|
15
|
-
const events = (await session.history()).filter(
|
|
16
|
-
(event) => event.index <= index,
|
|
17
|
-
)
|
|
12
|
+
const initial = await session.state()
|
|
18
13
|
|
|
19
|
-
return
|
|
20
|
-
<SessionProvider
|
|
21
|
-
sessionId={agentId}
|
|
22
|
-
initialState={state}
|
|
23
|
-
initialIndex={index}
|
|
24
|
-
initialEvents={events}
|
|
25
|
-
>
|
|
26
|
-
<AgentClient agentId={agentId} />
|
|
27
|
-
</SessionProvider>
|
|
28
|
-
)
|
|
14
|
+
return <AgentClient agentId={agentId} initial={initial} />
|
|
29
15
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { expect, it, vi } from 'vitest'
|
|
2
|
+
import { agent } from 'experimental-a2/ai/server'
|
|
3
|
+
import { memory } from 'experimental-a2/store-memory'
|
|
4
|
+
import { inputs } from 'experimental-a2/ai'
|
|
5
|
+
import { demoAgent } from './model'
|
|
6
|
+
|
|
7
|
+
const message = { id: 'message', role: 'user' as const, parts: [] }
|
|
8
|
+
|
|
9
|
+
const post = (events: unknown[]): Request =>
|
|
10
|
+
new Request('http://test/events?sessionId=one', {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
headers: { 'content-type': 'application/json' },
|
|
13
|
+
body: JSON.stringify({ sessionId: 'one', events }),
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const server = () => {
|
|
17
|
+
let generations = 0
|
|
18
|
+
const agentServer = agent({
|
|
19
|
+
contract: demoAgent,
|
|
20
|
+
store: memory(),
|
|
21
|
+
model: 'test/model',
|
|
22
|
+
generate: () => {
|
|
23
|
+
generations += 1
|
|
24
|
+
return new ReadableStream({ start: (controller) => controller.close() })
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
return { agentServer, generations: () => generations }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
it('accepts compaction settings from the browser without starting a turn', async () => {
|
|
31
|
+
const { agentServer, generations } = server()
|
|
32
|
+
const response = await agentServer.fetch(
|
|
33
|
+
post([
|
|
34
|
+
{
|
|
35
|
+
type: 'compaction.settings.updated',
|
|
36
|
+
payload: { mode: 'manual', thresholdTokens: 1 },
|
|
37
|
+
},
|
|
38
|
+
]),
|
|
39
|
+
)
|
|
40
|
+
expect(response.status).toBe(200)
|
|
41
|
+
await agentServer.drain('one')
|
|
42
|
+
const { state } = await agentServer.session('one').state()
|
|
43
|
+
expect(state.compactionSettings).toEqual({
|
|
44
|
+
mode: 'manual',
|
|
45
|
+
thresholdTokens: 1,
|
|
46
|
+
})
|
|
47
|
+
expect(generations()).toBe(0)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it.each([0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1, '1', null])(
|
|
51
|
+
'rejects invalid threshold %s',
|
|
52
|
+
async (thresholdTokens) => {
|
|
53
|
+
const { agentServer } = server()
|
|
54
|
+
const response = await agentServer.fetch(
|
|
55
|
+
post([
|
|
56
|
+
{
|
|
57
|
+
type: 'compaction.settings.updated',
|
|
58
|
+
payload: { mode: 'manual', thresholdTokens },
|
|
59
|
+
},
|
|
60
|
+
]),
|
|
61
|
+
)
|
|
62
|
+
expect(response.status).toBe(400)
|
|
63
|
+
expect(await agentServer.session('one').history()).toEqual([])
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
it('keeps lifecycle events server-only beside public controls', async () => {
|
|
68
|
+
const { agentServer } = server()
|
|
69
|
+
const rejected = await agentServer.fetch(
|
|
70
|
+
post([
|
|
71
|
+
...inputs.message(message),
|
|
72
|
+
{ type: 'ai.session.closed', payload: {} },
|
|
73
|
+
]),
|
|
74
|
+
)
|
|
75
|
+
expect(rejected.status).toBe(400)
|
|
76
|
+
expect(await agentServer.session('one').history()).toEqual([])
|
|
77
|
+
const accepted = await agentServer.fetch(post(inputs.message(message)))
|
|
78
|
+
expect(accepted.status).toBe(200)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('forwards both HTTP and WebSocket entry requests to the agent', async () => {
|
|
82
|
+
const fetch = vi
|
|
83
|
+
.fn<(request: Request) => Promise<Response>>()
|
|
84
|
+
.mockResolvedValue(new Response(null))
|
|
85
|
+
vi.doMock('./server', () => ({ agentServer: { fetch } }))
|
|
86
|
+
try {
|
|
87
|
+
const route = await import('./events/route')
|
|
88
|
+
const read = new Request('http://test/events', {
|
|
89
|
+
headers: { upgrade: 'websocket' },
|
|
90
|
+
})
|
|
91
|
+
const write = new Request('http://test/events', { method: 'POST' })
|
|
92
|
+
await route.GET(read)
|
|
93
|
+
await route.POST(write)
|
|
94
|
+
expect(fetch.mock.calls).toEqual([[read], [write]])
|
|
95
|
+
} finally {
|
|
96
|
+
vi.doUnmock('./server')
|
|
97
|
+
}
|
|
98
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
3
|
+
import { afterEach, expect, it, vi } from 'vitest'
|
|
4
|
+
import type { ModelChoice } from './model'
|
|
5
|
+
import { ModelInput } from './model-input'
|
|
6
|
+
|
|
7
|
+
afterEach(cleanup)
|
|
8
|
+
|
|
9
|
+
const renderInput = (value: ModelChoice) => {
|
|
10
|
+
const onChange = vi.fn<(settings: ModelChoice) => void>()
|
|
11
|
+
const view = render(
|
|
12
|
+
<ModelInput value={value} onChange={onChange} disabled={false} />,
|
|
13
|
+
)
|
|
14
|
+
return {
|
|
15
|
+
onChange,
|
|
16
|
+
model: screen.getByRole('textbox') as HTMLInputElement,
|
|
17
|
+
update: (next: ModelChoice) =>
|
|
18
|
+
view.rerender(
|
|
19
|
+
<ModelInput value={next} onChange={onChange} disabled={false} />,
|
|
20
|
+
),
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
it('commits a typed model on blur or Enter, not per keystroke', () => {
|
|
25
|
+
const { onChange, model, update } = renderInput({ reasoning: 'high' })
|
|
26
|
+
fireEvent.change(model, { target: { value: 'openai/gpt' } })
|
|
27
|
+
fireEvent.change(model, { target: { value: ' provider/model ' } })
|
|
28
|
+
expect(onChange).not.toHaveBeenCalled()
|
|
29
|
+
expect(model.value).toBe(' provider/model ')
|
|
30
|
+
fireEvent.blur(model)
|
|
31
|
+
expect(onChange).toHaveBeenCalledExactlyOnceWith({
|
|
32
|
+
reasoning: 'high',
|
|
33
|
+
model: 'provider/model',
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
update({ reasoning: 'high', model: 'provider/model' })
|
|
37
|
+
expect(model.value).toBe('provider/model')
|
|
38
|
+
fireEvent.change(model, { target: { value: '' } })
|
|
39
|
+
fireEvent.keyDown(model, { key: 'Enter' })
|
|
40
|
+
expect(onChange).toHaveBeenLastCalledWith({ reasoning: 'high' })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('commits reasoning and speed immediately and omits defaults', () => {
|
|
44
|
+
const { onChange } = renderInput({
|
|
45
|
+
model: 'provider/model',
|
|
46
|
+
reasoning: 'low',
|
|
47
|
+
})
|
|
48
|
+
fireEvent.change(screen.getByTitle('Thinking level'), {
|
|
49
|
+
target: { value: 'provider-default' },
|
|
50
|
+
})
|
|
51
|
+
expect(onChange).toHaveBeenLastCalledWith({ model: 'provider/model' })
|
|
52
|
+
fireEvent.click(screen.getByRole('button', { name: 'Prefer fast' }))
|
|
53
|
+
expect(onChange).toHaveBeenLastCalledWith({
|
|
54
|
+
model: 'provider/model',
|
|
55
|
+
reasoning: 'low',
|
|
56
|
+
speed: 'fast',
|
|
57
|
+
})
|
|
58
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, type ReactNode } from 'react'
|
|
4
|
+
import { reasoningLevels, type ReasoningLevel } from 'experimental-a2/ai'
|
|
5
|
+
import { agentModel, type ModelChoice } from './model'
|
|
6
|
+
|
|
7
|
+
const choose = <K extends keyof ModelChoice>(
|
|
8
|
+
settings: ModelChoice,
|
|
9
|
+
key: K,
|
|
10
|
+
value: ModelChoice[K] | undefined,
|
|
11
|
+
): ModelChoice => {
|
|
12
|
+
const { [key]: _previous, ...rest } = settings
|
|
13
|
+
return value === undefined ? rest : { ...rest, [key]: value }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ModelInput({
|
|
17
|
+
value,
|
|
18
|
+
onChange,
|
|
19
|
+
disabled,
|
|
20
|
+
}: {
|
|
21
|
+
value: ModelChoice
|
|
22
|
+
onChange: (settings: ModelChoice) => void
|
|
23
|
+
disabled: boolean
|
|
24
|
+
}): ReactNode {
|
|
25
|
+
// A choice is a durable session event, so a typed model id commits on
|
|
26
|
+
// blur or Enter rather than once per keystroke.
|
|
27
|
+
const [draft, setDraft] = useState<string | null>(null)
|
|
28
|
+
const model = draft ?? value.model ?? ''
|
|
29
|
+
const commit = (): void => {
|
|
30
|
+
if (draft === null) return
|
|
31
|
+
setDraft(null)
|
|
32
|
+
const next = draft.trim() || undefined
|
|
33
|
+
if (next !== value.model) onChange(choose(value, 'model', next))
|
|
34
|
+
}
|
|
35
|
+
return (
|
|
36
|
+
<div className="agent-model-controls">
|
|
37
|
+
<label className="agent-model-choice">
|
|
38
|
+
<span className="sr-only">Model</span>
|
|
39
|
+
<input
|
|
40
|
+
type="text"
|
|
41
|
+
value={model}
|
|
42
|
+
placeholder={agentModel}
|
|
43
|
+
size={Math.max(model.length || agentModel.length, 12)}
|
|
44
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
45
|
+
onBlur={commit}
|
|
46
|
+
onKeyDown={(event) => {
|
|
47
|
+
if (event.key !== 'Enter' || event.nativeEvent.isComposing) return
|
|
48
|
+
event.preventDefault()
|
|
49
|
+
commit()
|
|
50
|
+
}}
|
|
51
|
+
disabled={disabled}
|
|
52
|
+
autoComplete="off"
|
|
53
|
+
autoCorrect="off"
|
|
54
|
+
autoCapitalize="none"
|
|
55
|
+
spellCheck={false}
|
|
56
|
+
/>
|
|
57
|
+
</label>
|
|
58
|
+
<label className="agent-thinking-choice">
|
|
59
|
+
<span className="sr-only">Thinking level</span>
|
|
60
|
+
<select
|
|
61
|
+
title="Thinking level"
|
|
62
|
+
value={value.reasoning ?? 'provider-default'}
|
|
63
|
+
onChange={(event) => {
|
|
64
|
+
const level = event.target.value as ReasoningLevel
|
|
65
|
+
onChange(
|
|
66
|
+
choose(
|
|
67
|
+
value,
|
|
68
|
+
'reasoning',
|
|
69
|
+
level === 'provider-default' ? undefined : level,
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
}}
|
|
73
|
+
disabled={disabled}
|
|
74
|
+
>
|
|
75
|
+
{reasoningLevels.map((level) => (
|
|
76
|
+
<option key={level} value={level}>
|
|
77
|
+
{level === 'provider-default'
|
|
78
|
+
? 'Default'
|
|
79
|
+
: level === 'xhigh'
|
|
80
|
+
? 'Extra high'
|
|
81
|
+
: level[0]!.toUpperCase() + level.slice(1)}
|
|
82
|
+
</option>
|
|
83
|
+
))}
|
|
84
|
+
</select>
|
|
85
|
+
</label>
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
className="agent-fast-toggle"
|
|
89
|
+
aria-label="Prefer fast"
|
|
90
|
+
aria-pressed={value.speed === 'fast'}
|
|
91
|
+
title="Prefer fast: uses the faster tier when available. May cost more; otherwise runs at standard speed."
|
|
92
|
+
onClick={() =>
|
|
93
|
+
onChange(
|
|
94
|
+
choose(value, 'speed', value.speed === 'fast' ? undefined : 'fast'),
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
disabled={disabled}
|
|
98
|
+
>
|
|
99
|
+
<svg
|
|
100
|
+
width="18"
|
|
101
|
+
height="18"
|
|
102
|
+
viewBox="0 0 24 24"
|
|
103
|
+
fill={value.speed === 'fast' ? 'currentColor' : 'none'}
|
|
104
|
+
stroke="currentColor"
|
|
105
|
+
strokeWidth="1.7"
|
|
106
|
+
strokeLinecap="round"
|
|
107
|
+
strokeLinejoin="round"
|
|
108
|
+
aria-hidden="true"
|
|
109
|
+
>
|
|
110
|
+
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8Z" />
|
|
111
|
+
</svg>
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
agentModel,
|
|
4
|
+
compactionOptions,
|
|
5
|
+
demoAgent,
|
|
6
|
+
modelSettings,
|
|
7
|
+
type AgentEvent,
|
|
8
|
+
} from './model'
|
|
9
|
+
|
|
10
|
+
const event = <T extends AgentEvent['type']>(
|
|
11
|
+
index: number,
|
|
12
|
+
type: T,
|
|
13
|
+
payload: Extract<AgentEvent, { type: T }>['payload'],
|
|
14
|
+
): AgentEvent =>
|
|
15
|
+
({
|
|
16
|
+
id: `event-${index}`,
|
|
17
|
+
sessionId: 's',
|
|
18
|
+
index,
|
|
19
|
+
createdAt: new Date(0),
|
|
20
|
+
type,
|
|
21
|
+
payload,
|
|
22
|
+
}) as AgentEvent
|
|
23
|
+
|
|
24
|
+
it('folds compaction settings beside the AI state', () => {
|
|
25
|
+
const { reducer } = demoAgent
|
|
26
|
+
let state = reducer.fold(
|
|
27
|
+
reducer.initialState,
|
|
28
|
+
event(1, 'compaction.settings.updated', { mode: 'disabled' }),
|
|
29
|
+
)
|
|
30
|
+
state = reducer.fold(
|
|
31
|
+
state,
|
|
32
|
+
event(2, 'ai.message.created', {
|
|
33
|
+
message: { id: 'user', role: 'user', parts: [] },
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
36
|
+
expect(state.compactionSettings).toEqual({ mode: 'disabled' })
|
|
37
|
+
expect(state.compactionSettingsId).toBe('event-1')
|
|
38
|
+
expect(state.messages.map((message) => message.id)).toEqual(['user'])
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it.each([
|
|
42
|
+
[{ mode: 'automatic' } as const, {}],
|
|
43
|
+
[{ mode: 'manual', thresholdTokens: 1 } as const, { thresholdTokens: 1 }],
|
|
44
|
+
[{ mode: 'disabled' } as const, false],
|
|
45
|
+
])('maps %j to compaction options', (settings, options) => {
|
|
46
|
+
const state = demoAgent.reducer.fold(
|
|
47
|
+
demoAgent.reducer.initialState,
|
|
48
|
+
event(1, 'compaction.settings.updated', settings),
|
|
49
|
+
)
|
|
50
|
+
expect(compactionOptions(state.compactionSettings)).toEqual(options)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('folds the model choice and fills in the default model', () => {
|
|
54
|
+
const { reducer } = demoAgent
|
|
55
|
+
const state = reducer.fold(
|
|
56
|
+
reducer.initialState,
|
|
57
|
+
event(1, 'model.choice.updated', { reasoning: 'high', speed: 'fast' }),
|
|
58
|
+
)
|
|
59
|
+
expect(reducer.initialState.modelChoice).toEqual({})
|
|
60
|
+
expect(modelSettings(state.modelChoice)).toEqual({
|
|
61
|
+
model: agentModel,
|
|
62
|
+
reasoning: 'high',
|
|
63
|
+
speed: 'fast',
|
|
64
|
+
})
|
|
65
|
+
expect(
|
|
66
|
+
modelSettings(
|
|
67
|
+
reducer.fold(
|
|
68
|
+
state,
|
|
69
|
+
event(2, 'model.choice.updated', { model: 'openai/gpt-5.6-terra' }),
|
|
70
|
+
).modelChoice,
|
|
71
|
+
),
|
|
72
|
+
).toEqual({ model: 'openai/gpt-5.6-terra' })
|
|
73
|
+
})
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { agent } from 'experimental-a2/ai'
|
|
2
1
|
import type { ContractEvent } from 'experimental-a2'
|
|
3
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
agentContract,
|
|
4
|
+
reasoningLevels,
|
|
5
|
+
type ReasoningLevel,
|
|
6
|
+
} from 'experimental-a2/ai'
|
|
7
|
+
import type {
|
|
8
|
+
AutomaticCompaction,
|
|
9
|
+
ModelSettings,
|
|
10
|
+
} from 'experimental-a2/ai/server'
|
|
4
11
|
import { z } from 'zod'
|
|
5
12
|
|
|
6
13
|
export const agentModel = 'openai/gpt-5.6-luna'
|
|
@@ -16,36 +23,61 @@ export const compactionSettingsSchema = z.discriminatedUnion('mode', [
|
|
|
16
23
|
|
|
17
24
|
export type CompactionSettings = z.infer<typeof compactionSettingsSchema>
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
/** The picker's choice; an omitted field keeps the agent's default. */
|
|
27
|
+
export const modelChoiceSchema = z.strictObject({
|
|
28
|
+
model: z.string().trim().min(1).optional(),
|
|
29
|
+
reasoning: z
|
|
30
|
+
.string()
|
|
31
|
+
.refine((value): value is ReasoningLevel =>
|
|
32
|
+
reasoningLevels.includes(value as ReasoningLevel),
|
|
33
|
+
)
|
|
34
|
+
.optional(),
|
|
35
|
+
speed: z.enum(['fast', 'standard']).optional(),
|
|
22
36
|
})
|
|
23
37
|
|
|
24
|
-
export type
|
|
38
|
+
export type ModelChoice = z.infer<typeof modelChoiceSchema>
|
|
25
39
|
|
|
26
40
|
const automatic: CompactionSettings = { mode: 'automatic' }
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
? { settings: event.payload, id: event.id }
|
|
37
|
-
: { settings: automatic, id: 'default' }
|
|
41
|
+
const noChoice: ModelChoice = {}
|
|
42
|
+
const settingsState: {
|
|
43
|
+
compactionSettings: CompactionSettings
|
|
44
|
+
compactionSettingsId: string
|
|
45
|
+
modelChoice: ModelChoice
|
|
46
|
+
} = {
|
|
47
|
+
compactionSettings: automatic,
|
|
48
|
+
compactionSettingsId: 'default',
|
|
49
|
+
modelChoice: noChoice,
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
export const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
export const demoAgent = agentContract({
|
|
53
|
+
name: 'agent',
|
|
54
|
+
events: {
|
|
55
|
+
'compaction.settings.updated': compactionSettingsSchema,
|
|
56
|
+
'model.choice.updated': modelChoiceSchema,
|
|
57
|
+
},
|
|
58
|
+
reducer: {
|
|
59
|
+
name: 'a2.playground.agent.v4',
|
|
60
|
+
initialState: settingsState,
|
|
61
|
+
fold: (state, event) =>
|
|
62
|
+
event.type === 'compaction.settings.updated'
|
|
63
|
+
? {
|
|
64
|
+
...state,
|
|
65
|
+
compactionSettings: event.payload,
|
|
66
|
+
compactionSettingsId: event.id,
|
|
67
|
+
}
|
|
68
|
+
: event.type === 'model.choice.updated'
|
|
69
|
+
? { ...state, modelChoice: event.payload }
|
|
70
|
+
: state,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
export type AgentEvent = ContractEvent<typeof demoAgent.events>
|
|
75
|
+
export type AgentState = typeof demoAgent.reducer.initialState
|
|
76
|
+
|
|
77
|
+
export const modelSettings = (choice: ModelChoice): ModelSettings => ({
|
|
78
|
+
...choice,
|
|
79
|
+
model: choice.model ?? agentModel,
|
|
80
|
+
})
|
|
49
81
|
|
|
50
82
|
export function compactionOptions(
|
|
51
83
|
settings: CompactionSettings,
|
|
@@ -4,11 +4,12 @@ import type {
|
|
|
4
4
|
KeyboardEvent as ReactKeyboardEvent,
|
|
5
5
|
ReactNode,
|
|
6
6
|
} from 'react'
|
|
7
|
-
import { useState } from 'react'
|
|
7
|
+
import { useId, useState } from 'react'
|
|
8
8
|
import { useRouter } from 'next/navigation'
|
|
9
|
-
import type { UIMessage } from 'ai'
|
|
10
9
|
import { inputs } from 'experimental-a2/ai'
|
|
10
|
+
import type { ModelChoice } from './model'
|
|
11
11
|
import { agentClient } from './session'
|
|
12
|
+
import { ModelInput } from './model-input'
|
|
12
13
|
|
|
13
14
|
const REMINDER_PROMPT =
|
|
14
15
|
'Set a reminder for yourself in 37 seconds to check checkout-api again.'
|
|
@@ -33,6 +34,8 @@ export function NewAgentSession({
|
|
|
33
34
|
remindersEnabled: boolean
|
|
34
35
|
}): ReactNode {
|
|
35
36
|
const router = useRouter()
|
|
37
|
+
const promptId = useId()
|
|
38
|
+
const [choice, setChoice] = useState<ModelChoice>({})
|
|
36
39
|
const [prompt, setPrompt] = useState(
|
|
37
40
|
remindersEnabled ? REMINDER_PROMPT : INCIDENT_PROMPT,
|
|
38
41
|
)
|
|
@@ -46,17 +49,21 @@ export function NewAgentSession({
|
|
|
46
49
|
if (!text || submitting) return
|
|
47
50
|
|
|
48
51
|
const agentId = crypto.randomUUID().slice(0, 8)
|
|
49
|
-
const message: UIMessage = {
|
|
50
|
-
id: crypto.randomUUID(),
|
|
51
|
-
role: 'user',
|
|
52
|
-
parts: [{ type: 'text', text }],
|
|
53
|
-
}
|
|
54
52
|
|
|
55
53
|
setPrompt('')
|
|
56
54
|
setSubmitting(true)
|
|
57
55
|
setError(null)
|
|
58
56
|
try {
|
|
59
|
-
await agentClient.session(agentId).push(
|
|
57
|
+
await agentClient.session(agentId).push(
|
|
58
|
+
...(Object.keys(choice).length === 0
|
|
59
|
+
? []
|
|
60
|
+
: [{ type: 'model.choice.updated' as const, payload: choice }]),
|
|
61
|
+
...inputs.message({
|
|
62
|
+
id: crypto.randomUUID(),
|
|
63
|
+
role: 'user',
|
|
64
|
+
parts: [{ type: 'text', text }],
|
|
65
|
+
}),
|
|
66
|
+
)
|
|
60
67
|
router.push(`/agent/${agentId}`)
|
|
61
68
|
} catch (err) {
|
|
62
69
|
setPrompt((current) => (current === '' ? submittedPrompt : current))
|
|
@@ -75,17 +82,28 @@ export function NewAgentSession({
|
|
|
75
82
|
</p>
|
|
76
83
|
</div>
|
|
77
84
|
<form className="agent-form" onSubmit={(event) => void start(event)}>
|
|
78
|
-
<
|
|
79
|
-
Message
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
<div className="agent-message-field">
|
|
86
|
+
<label htmlFor={promptId}>Message</label>
|
|
87
|
+
<div className="agent-composer">
|
|
88
|
+
<textarea
|
|
89
|
+
id={promptId}
|
|
90
|
+
autoFocus
|
|
91
|
+
rows={3}
|
|
92
|
+
placeholder="Message the agent…"
|
|
93
|
+
value={prompt}
|
|
94
|
+
onChange={(event) => setPrompt(event.target.value)}
|
|
95
|
+
onKeyDown={submitOnEnter}
|
|
96
|
+
disabled={submitting}
|
|
97
|
+
/>
|
|
98
|
+
<div className="agent-composer-toolbar">
|
|
99
|
+
<ModelInput
|
|
100
|
+
value={choice}
|
|
101
|
+
onChange={setChoice}
|
|
102
|
+
disabled={submitting}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
89
107
|
<div className="agent-form-actions">
|
|
90
108
|
<button type="submit" disabled={submitting || !prompt.trim()}>
|
|
91
109
|
{submitting ? 'Opening…' : 'Send to agent'}
|
|
@@ -15,10 +15,10 @@ export default function AgentPage(): ReactNode {
|
|
|
15
15
|
<NewAgentSession remindersEnabled={agentScheduler !== undefined} />
|
|
16
16
|
<p className="hint">
|
|
17
17
|
Each durable generation runs one AI SDK <code>streamText</code> step
|
|
18
|
-
with
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
with your chosen model through Vercel AI Gateway. Local development
|
|
19
|
+
needs Gateway credentials and a linked Vercel project for Sandbox;
|
|
20
|
+
Vercel deployments use their OIDC token. Reminder delivery also needs
|
|
21
|
+
the configured scheduler.
|
|
22
22
|
</p>
|
|
23
23
|
</article>
|
|
24
24
|
)
|
|
@@ -4,16 +4,13 @@ import { createHash } from 'node:crypto'
|
|
|
4
4
|
import { Sandbox } from '@vercel/sandbox'
|
|
5
5
|
import { tool } from 'ai'
|
|
6
6
|
import { handlerContext, inputs } from 'experimental-a2/ai'
|
|
7
|
-
import {
|
|
7
|
+
import { agent } from 'experimental-a2/ai/server'
|
|
8
8
|
import { vercelQueues } from 'experimental-a2/scheduler-vercel'
|
|
9
|
+
import { otel } from 'experimental-a2/otel'
|
|
9
10
|
import { z } from 'zod'
|
|
10
11
|
import { store } from '@/lib/store'
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
compactionOptions,
|
|
14
|
-
compactionSettings,
|
|
15
|
-
demoAgent,
|
|
16
|
-
} from './model'
|
|
12
|
+
import { modelTelemetry, traceMetadata } from './telemetry'
|
|
13
|
+
import { compactionOptions, demoAgent, modelSettings } from './model'
|
|
17
14
|
|
|
18
15
|
const maxReminderDelaySeconds = 6 * 24 * 60 * 60
|
|
19
16
|
const bashTimeoutMs = 120_000
|
|
@@ -132,13 +129,16 @@ export const agentScheduler =
|
|
|
132
129
|
? vercelQueues({ topic: 'a2-agent' })
|
|
133
130
|
: undefined
|
|
134
131
|
|
|
135
|
-
|
|
132
|
+
const telemetry = otel()
|
|
133
|
+
|
|
134
|
+
export const agentServer = agent({
|
|
135
|
+
contract: demoAgent,
|
|
136
136
|
...(store ? { store } : {}),
|
|
137
137
|
...(agentScheduler ? { scheduler: agentScheduler } : {}),
|
|
138
|
-
|
|
139
|
-
model:
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
telemetry,
|
|
139
|
+
model: ({ state }) => modelSettings(state.modelChoice),
|
|
140
|
+
messageMetadata: traceMetadata,
|
|
141
|
+
compaction: ({ state }) => compactionOptions(state.compactionSettings),
|
|
142
142
|
tools,
|
|
143
143
|
instructions: `You are an incident-response agent for the playground.
|
|
144
144
|
When responding to an incident, inspect checkout-api before taking action. Its
|
|
@@ -154,7 +154,8 @@ it again.
|
|
|
154
154
|
|
|
155
155
|
When the operator asks you to run a shell command, call bash. Make one bash call
|
|
156
156
|
at a time and wait for its result before calling it again. Be concise.`,
|
|
157
|
-
generation: {
|
|
157
|
+
generation: () => ({
|
|
158
|
+
telemetry: modelTelemetry(),
|
|
158
159
|
toolApproval: { restartService: 'user-approval' },
|
|
159
|
-
},
|
|
160
|
+
}),
|
|
160
161
|
})
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import { createClient } from 'experimental-a2/client'
|
|
3
|
-
import {
|
|
4
|
-
import { demoAgent } from './model'
|
|
3
|
+
import type { UseSessionResult } from 'experimental-a2/react'
|
|
4
|
+
import { demoAgent, type AgentState } from './model'
|
|
5
5
|
|
|
6
|
+
/** Shared by the first push and the session page, so one live session serves both. */
|
|
6
7
|
export const agentClient = createClient({
|
|
7
|
-
|
|
8
|
+
contract: demoAgent,
|
|
8
9
|
api: '/agent/events',
|
|
9
10
|
})
|
|
10
11
|
|
|
11
|
-
export
|
|
12
|
-
client: agentClient,
|
|
13
|
-
})
|
|
12
|
+
export type AgentSession = UseSessionResult<typeof demoAgent.events, AgentState>
|