experimental-a2 0.17.0 → 0.18.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 +21 -0
- package/README.md +1 -1
- package/dist/{actor-DBbe5xeE.d.ts → actor-Bjce9_5U.d.ts} +5 -5
- package/dist/{actor-DBbe5xeE.d.ts.map → actor-Bjce9_5U.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +2 -2
- package/dist/{ai-UrWfKeuv.js → ai-BpOGm-zB.js} +2 -2
- package/dist/{ai-UrWfKeuv.js.map → ai-BpOGm-zB.js.map} +1 -1
- package/dist/{ai-vpGtcaCB.d.ts → ai-DrUaMyRt.d.ts} +3 -3
- package/dist/{ai-vpGtcaCB.d.ts.map → ai-DrUaMyRt.d.ts.map} +1 -1
- package/dist/ai-client.d.ts +2 -2
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.browser.d.ts +1 -0
- package/dist/ai-fx.browser.js +6 -0
- package/dist/ai-fx.browser.js.map +1 -0
- package/dist/ai-fx.d.ts +10 -0
- package/dist/ai-fx.d.ts.map +1 -0
- package/dist/ai-fx.js +634 -0
- package/dist/ai-fx.js.map +1 -0
- package/dist/ai-harness-BuLh_EYi.js +273 -0
- package/dist/ai-harness-BuLh_EYi.js.map +1 -0
- package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
- package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
- package/dist/ai-pi.browser.d.ts +1 -0
- package/dist/ai-pi.browser.js +6 -0
- package/dist/ai-pi.browser.js.map +1 -0
- package/dist/ai-pi.d.ts +17 -0
- package/dist/ai-pi.d.ts.map +1 -0
- package/dist/ai-pi.js +904 -0
- package/dist/ai-pi.js.map +1 -0
- package/dist/ai-react.d.ts +5 -5
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +47 -25
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +106 -37
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +1 -1
- package/dist/ai.js +1 -1
- package/dist/bounded-cache-CPHWF-8h.js +41 -0
- package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
- package/dist/{client-COaiaDub.js → client-BReKqwZC.js} +3 -3
- package/dist/{client-COaiaDub.js.map → client-BReKqwZC.js.map} +1 -1
- package/dist/{client-qRnRUzBS.d.ts → client-h_DKFAjj.d.ts} +3 -3
- package/dist/{client-qRnRUzBS.d.ts.map → client-h_DKFAjj.d.ts.map} +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/{initial-snapshot-BrLso5x_.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +2 -2
- package/dist/{initial-snapshot-BrLso5x_.d.ts.map → initial-snapshot-Bx-c0pDP.d.ts.map} +1 -1
- package/dist/{internal-CbVvEwad.js → internal-B2T3U4Rq.js} +8 -2
- package/dist/internal-B2T3U4Rq.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/{react-BbJ-g6Uu.d.ts → react-BBXgHWFc.d.ts} +4 -4
- package/dist/{react-BbJ-g6Uu.d.ts.map → react-BBXgHWFc.d.ts.map} +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/reducer-BAXc8JyF.js.map +1 -1
- package/dist/{reducer-BYu6DT7h.d.ts → reducer-CqA59Q6U.d.ts} +2 -2
- package/dist/{reducer-BYu6DT7h.d.ts.map → reducer-CqA59Q6U.d.ts.map} +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +2 -2
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +2 -2
- package/dist/{server-Rj64fNvX.js → server-Dw_k_7kO.js} +93 -60
- package/dist/server-Dw_k_7kO.js.map +1 -0
- package/dist/{server-CGM8WzRG.d.ts → server-En7cbi3v.d.ts} +36 -7
- package/dist/server-En7cbi3v.d.ts.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +1 -1
- package/dist/{session-clear-Dshpuhff.d.ts → session-clear-Bo-XTFEy.d.ts} +2 -2
- package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
- package/dist/{store-Gpkq7WBo.d.ts → store-Djdv0bnZ.d.ts} +31 -2
- package/dist/store-Djdv0bnZ.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.js +32 -6
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-4_y8lETm.js → store-polling-B5FcwJfo.js} +2 -2
- package/dist/{store-polling-4_y8lETm.js.map → store-polling-B5FcwJfo.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.js +54 -3
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-Q1C-k2dd.js → store-presence-polling-BAwS8RWn.js} +3 -3
- package/dist/{store-presence-polling-Q1C-k2dd.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +3 -3
- package/dist/{store-redis-notify-tqWNnMRu.js → store-redis-notify-BRITti_o.js} +187 -68
- package/dist/store-redis-notify-BRITti_o.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.js +32 -2
- package/dist/store-sqlite.js.map +1 -1
- package/dist/{telemetry-cZH-cibj.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
- package/dist/{wire-DjfHOvdW.js → wire-rSUglYo8.js} +2 -2
- package/dist/{wire-DjfHOvdW.js.map → wire-rSUglYo8.js.map} +1 -1
- package/docs/01-quickstart.mdx +49 -83
- package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
- package/docs/actors/01-introduction.mdx +48 -86
- package/docs/actors/02-concurrency.mdx +43 -70
- package/docs/actors/03-timers.mdx +28 -42
- package/docs/actors/04-routes.mdx +110 -150
- package/docs/actors/meta.ts +1 -1
- package/docs/agents/01-introduction.mdx +257 -0
- package/docs/agents/02-tools.mdx +247 -0
- package/docs/agents/03-conversations.mdx +538 -0
- package/docs/agents/04-harnesses.mdx +136 -0
- package/docs/agents/meta.ts +1 -0
- package/docs/client/01-react.mdx +294 -0
- package/docs/client/02-transports.mdx +86 -0
- package/docs/client/03-presence.mdx +224 -0
- package/docs/client/meta.ts +1 -0
- package/docs/contract/01-events.mdx +57 -0
- package/docs/contract/02-state.mdx +142 -0
- package/docs/contract/03-sessions.mdx +175 -0
- package/docs/index.mdx +108 -144
- package/docs/reference/01-core.mdx +139 -0
- package/docs/reference/02-server.mdx +855 -0
- package/docs/reference/03-client.mdx +301 -0
- package/docs/reference/04-ai.mdx +722 -0
- package/docs/reference/05-adapters.mdx +351 -0
- package/docs/reference/06-errors.mdx +138 -0
- package/docs/reference/meta.ts +1 -1
- package/docs/server/01-handlers.mdx +200 -0
- package/docs/server/02-timers.mdx +130 -0
- package/docs/server/03-cancellation.mdx +141 -0
- package/docs/server/04-routes.mdx +243 -0
- package/docs/server/05-production.mdx +322 -0
- package/docs/server/06-guarantees.mdx +123 -0
- package/examples/playground/DEPLOY.md +1 -1
- package/examples/playground/app/a0/[appId]/page.tsx +11 -6
- package/examples/playground/app/a0/app-list.tsx +9 -2
- package/examples/playground/app/a0/app-view.tsx +9 -1
- package/examples/playground/app/a0/apps/[appId]/route.ts +10 -5
- package/examples/playground/app/a0/apps.test.ts +12 -0
- package/examples/playground/app/a0/apps.ts +22 -7
- package/examples/playground/app/a0/chat.tsx +47 -1
- package/examples/playground/app/a0/fx/events/route.ts +9 -0
- package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
- package/examples/playground/app/a0/model.test.ts +18 -1
- package/examples/playground/app/a0/model.ts +47 -10
- package/examples/playground/app/a0/no-photon.cjs +4 -0
- package/examples/playground/app/a0/no-quickjs.cjs +3 -0
- package/examples/playground/app/a0/pi/events/route.ts +9 -0
- package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
- package/examples/playground/app/a0/sandbox-log.ts +10 -4
- package/examples/playground/app/a0/sandbox-session.ts +152 -0
- package/examples/playground/app/a0/scheduler/route.ts +2 -2
- package/examples/playground/app/a0/server.ts +100 -45
- package/examples/playground/app/a0/session.ts +17 -5
- package/examples/playground/app/a0/shell.tsx +11 -3
- package/examples/playground/app/agent/ingress.test.ts +6 -3
- package/examples/playground/app/agent/server.ts +9 -6
- package/examples/playground/app/agent/telemetry.test.ts +71 -50
- package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +1 -1
- package/examples/playground/app/chat/server.test.ts +30 -26
- package/examples/playground/app/chat/server.ts +46 -46
- package/examples/playground/next.config.ts +17 -5
- package/examples/playground/package.json +4 -1
- package/examples/playground/vercel.json +18 -0
- package/package.json +27 -3
- package/src/ai-agent.ts +261 -87
- package/src/ai-fx.browser.ts +5 -0
- package/src/ai-fx.ts +833 -0
- package/src/ai-harness.ts +518 -0
- package/src/ai-model-metadata.ts +11 -6
- package/src/ai-pi.browser.ts +5 -0
- package/src/ai-pi.ts +1263 -0
- package/src/ai-sdk-step.ts +2 -2
- package/src/ai-server.ts +3 -1
- package/src/internal.ts +7 -0
- package/src/libfx.d.ts +34 -0
- package/src/reducer.ts +1 -1
- package/src/server.ts +177 -19
- package/src/store-memory.ts +42 -7
- package/src/store-postgres.ts +80 -0
- package/src/store-redis-core.ts +210 -79
- package/src/store-redis-notify.ts +2 -0
- package/src/store-sqlite.ts +60 -0
- package/src/store.ts +33 -0
- package/src/telemetry.ts +1 -0
- package/dist/internal-CbVvEwad.js.map +0 -1
- package/dist/server-CGM8WzRG.d.ts.map +0 -1
- package/dist/server-Rj64fNvX.js.map +0 -1
- package/dist/session-clear-Dshpuhff.d.ts.map +0 -1
- package/dist/store-Gpkq7WBo.d.ts.map +0 -1
- package/dist/store-redis-notify-tqWNnMRu.js.map +0 -1
- package/dist/telemetry-cZH-cibj.d.ts.map +0 -1
- package/docs/concepts/01-contracts.mdx +0 -237
- package/docs/concepts/02-handlers.mdx +0 -284
- package/docs/concepts/03-durability.mdx +0 -314
- package/docs/concepts/04-state.mdx +0 -209
- package/docs/guides/01-timers.mdx +0 -193
- package/docs/guides/02-cancellation.mdx +0 -161
- package/docs/guides/03-react.mdx +0 -324
- package/docs/guides/04-local-first.mdx +0 -47
- package/docs/guides/05-production.mdx +0 -534
- package/docs/guides/06-ai-agents.mdx +0 -1408
- package/docs/guides/08-application-data.mdx +0 -113
- package/docs/guides/09-presence.mdx +0 -262
- package/docs/guides/10-transports.mdx +0 -221
- package/docs/reference/01-api.mdx +0 -2358
- package/docs/reference/02-errors.mdx +0 -150
- /package/docs/{concepts → contract}/meta.ts +0 -0
- /package/docs/{guides → server}/meta.ts +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { agentServer,
|
|
1
|
+
import { agentServer, appSchedulers } from '../server'
|
|
2
2
|
|
|
3
3
|
export const POST =
|
|
4
|
-
|
|
4
|
+
appSchedulers['ai-sdk']?.handler(agentServer) ??
|
|
5
5
|
(() => new Response('a0 scheduler is not configured', { status: 503 }))
|
|
6
6
|
|
|
7
7
|
export const maxDuration = 300
|
|
@@ -2,6 +2,8 @@ import 'server-only'
|
|
|
2
2
|
import { waitUntil } from '@vercel/functions'
|
|
3
3
|
import { tool } from 'ai'
|
|
4
4
|
import { handlerContext } from 'experimental-a2/ai'
|
|
5
|
+
import { fx } from 'experimental-a2/ai/fx'
|
|
6
|
+
import { pi } from 'experimental-a2/ai/pi'
|
|
5
7
|
import { agent } from 'experimental-a2/ai/server'
|
|
6
8
|
import { createServer } from 'experimental-a2/server'
|
|
7
9
|
import { vercelQueues } from 'experimental-a2/scheduler-vercel'
|
|
@@ -10,7 +12,7 @@ import { store } from '@/lib/store'
|
|
|
10
12
|
import { appList, isAppId } from './apps'
|
|
11
13
|
import { crashOnRequest } from './crash-on-request'
|
|
12
14
|
import { crashLog, crashLogId, isCrashLogId } from './crashes'
|
|
13
|
-
import {
|
|
15
|
+
import { appPort, type Harness, harnessAgents, type ModelChoice } from './model'
|
|
14
16
|
import {
|
|
15
17
|
appDir,
|
|
16
18
|
appSandbox,
|
|
@@ -19,8 +21,11 @@ import {
|
|
|
19
21
|
START_DEV,
|
|
20
22
|
workspace,
|
|
21
23
|
} from './sandbox'
|
|
24
|
+
import { sandboxSession } from './sandbox-session'
|
|
22
25
|
|
|
23
|
-
const
|
|
26
|
+
const instructions = (
|
|
27
|
+
tools: string,
|
|
28
|
+
): string => `You are a coding agent that builds a Next.js app in your own Linux sandbox, ${tools}. The user watches the app in a live preview next to this chat.
|
|
24
29
|
- ${appDir} already holds a blank Next.js app (App Router, TypeScript, Tailwind) with its dependencies installed, and next dev serves it on port ${appPort} with hot reload. It may still be starting when you begin: don't wait for it, poll it, or start another one. Build what the user asks for there; don't create another app.
|
|
25
30
|
- Until you change them, app/page.tsx renders an empty <main />, app/layout.tsx only sets the title, and app/globals.css only imports Tailwind. Write them directly; there's no need to look first.
|
|
26
31
|
- Assume your edits work. Don't curl the dev server, sleep, or read the page back to check, and don't run builds, type checks or lint unless the user asks.
|
|
@@ -48,9 +53,7 @@ const tools = {
|
|
|
48
53
|
execute: ({ path }) =>
|
|
49
54
|
timed(async () =>
|
|
50
55
|
previewUrl({
|
|
51
|
-
sandbox: await appSandbox({
|
|
52
|
-
appId: handlerContext(appAgent).session.id,
|
|
53
|
-
}),
|
|
56
|
+
sandbox: await appSandbox({ appId: handlerContext().session.id }),
|
|
54
57
|
path,
|
|
55
58
|
}),
|
|
56
59
|
),
|
|
@@ -63,7 +66,7 @@ const tools = {
|
|
|
63
66
|
timeoutSeconds: z.number().int().min(1).max(240).default(120),
|
|
64
67
|
}),
|
|
65
68
|
execute: ({ command, timeoutSeconds }) => {
|
|
66
|
-
const context = handlerContext(
|
|
69
|
+
const context = handlerContext()
|
|
67
70
|
return timed(async () =>
|
|
68
71
|
bash({
|
|
69
72
|
sandbox: await appSandbox({ appId: context.session.id }),
|
|
@@ -76,56 +79,108 @@ const tools = {
|
|
|
76
79
|
}),
|
|
77
80
|
}
|
|
78
81
|
|
|
82
|
+
const queued = !!(process.env.VERCEL || process.env.A2_QUEUES)
|
|
83
|
+
|
|
79
84
|
/**
|
|
80
85
|
* Wakes agent work in a fresh invocation when the one that started it ends,
|
|
81
|
-
* and recovers from crashed ones.
|
|
86
|
+
* and recovers from crashed ones. Each harness's agent has its own topic.
|
|
82
87
|
*/
|
|
83
|
-
export const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
export const appSchedulers = {
|
|
89
|
+
'ai-sdk': queued ? vercelQueues({ topic: 'a2-a0' }) : undefined,
|
|
90
|
+
fx: queued ? vercelQueues({ topic: 'a2-a0-fx' }) : undefined,
|
|
91
|
+
pi: queued ? vercelQueues({ topic: 'a2-a0-pi' }) : undefined,
|
|
92
|
+
}
|
|
87
93
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The app's model choice for a step. The browser pushes it, and the
|
|
96
|
+
* contract's schema checks it on the way in.
|
|
97
|
+
*/
|
|
98
|
+
function chosenModel({
|
|
99
|
+
harness,
|
|
100
|
+
event,
|
|
101
|
+
state,
|
|
102
|
+
}: {
|
|
103
|
+
harness: Harness
|
|
104
|
+
event: { sessionId: string }
|
|
105
|
+
state: { modelChoice: ModelChoice }
|
|
106
|
+
}) {
|
|
107
|
+
// Every step starts here, in the process that runs it, which is what
|
|
108
|
+
// the dev tools' crash has to end.
|
|
109
|
+
if (crashesAllowed) {
|
|
110
|
+
const appId = event.sessionId
|
|
111
|
+
waitUntil(
|
|
112
|
+
crashOnRequest({
|
|
113
|
+
appId,
|
|
114
|
+
app: agentServers[harness].session(appId),
|
|
115
|
+
log: crashServer.session(crashLogId(appId)),
|
|
116
|
+
}).catch(() => {}),
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
const { model, reasoning, fast } = state.modelChoice
|
|
120
|
+
return { model, reasoning, speed: fast ? 'fast' : 'standard' } as const
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const shared = (harness: Harness) => ({
|
|
91
124
|
...(store ? { store } : {}),
|
|
92
|
-
...(
|
|
93
|
-
contract:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return { model, reasoning, speed: fast ? 'fast' : 'standard' }
|
|
125
|
+
...(appSchedulers[harness] ? { scheduler: appSchedulers[harness] } : {}),
|
|
126
|
+
contract: harnessAgents[harness],
|
|
127
|
+
maxSteps: 80,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
export const agentServer = agent({
|
|
131
|
+
...shared('ai-sdk'),
|
|
132
|
+
model: ({ event, state }) => chosenModel({ harness: 'ai-sdk', event, state }),
|
|
133
|
+
harness: {
|
|
134
|
+
type: 'ai-sdk',
|
|
135
|
+
generation: () => ({
|
|
136
|
+
// Claude leaves its thinking's text out unless asked for a summary.
|
|
137
|
+
providerOptions: {
|
|
138
|
+
anthropic: { thinking: { type: 'adaptive', display: 'summarized' } },
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
109
141
|
},
|
|
110
|
-
|
|
111
|
-
// Claude leaves its thinking's text out unless asked for a summary.
|
|
112
|
-
providerOptions: {
|
|
113
|
-
anthropic: { thinking: { type: 'adaptive', display: 'summarized' } },
|
|
114
|
-
},
|
|
115
|
-
}),
|
|
116
|
-
instructions: INSTRUCTIONS,
|
|
142
|
+
instructions: instructions('through the bash tool'),
|
|
117
143
|
tools,
|
|
118
|
-
maxSteps: 80,
|
|
119
144
|
})
|
|
120
145
|
|
|
121
|
-
/**
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
146
|
+
/** FX's own loop, with the same tools and prompt. */
|
|
147
|
+
const fxServer = agent({
|
|
148
|
+
...shared('fx'),
|
|
149
|
+
harness: fx(),
|
|
150
|
+
model: ({ event, state }) => chosenModel({ harness: 'fx', event, state }),
|
|
151
|
+
instructions: instructions('through the bash tool'),
|
|
152
|
+
tools,
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
/** Pi's own loop and tools, working in the app's sandbox, plus the preview. */
|
|
156
|
+
const piServer = agent({
|
|
157
|
+
...shared('pi'),
|
|
158
|
+
harness: pi({
|
|
159
|
+
sandbox: async ({ session }) =>
|
|
160
|
+
sandboxSession(await appSandbox({ appId: session.id })),
|
|
161
|
+
}),
|
|
162
|
+
model: ({ event, state }) => chosenModel({ harness: 'pi', event, state }),
|
|
163
|
+
instructions: instructions('with your tools'),
|
|
164
|
+
tools: { show_preview: tools.show_preview },
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
export const agentServers = {
|
|
168
|
+
'ai-sdk': agentServer,
|
|
169
|
+
fx: fxServer,
|
|
170
|
+
pi: piServer,
|
|
127
171
|
}
|
|
128
172
|
|
|
173
|
+
/** An app's event stream and pushes, for one harness's agent; only an app id names a session. */
|
|
174
|
+
export const harnessEvents =
|
|
175
|
+
(harness: Harness) =>
|
|
176
|
+
(request: Request): Promise<Response> =>
|
|
177
|
+
agentServers[harness].fetch(request, {
|
|
178
|
+
// The agent refuses the AI protocol's server-authored events itself.
|
|
179
|
+
authorize: (operation) => isAppId(operation.sessionId),
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
export const appEvents = harnessEvents('ai-sdk')
|
|
183
|
+
|
|
129
184
|
/** The apps sidebar's lists, which only the browser writes. */
|
|
130
185
|
export const listServer = createServer({
|
|
131
186
|
...(store ? { store } : {}),
|
|
@@ -3,7 +3,7 @@ import { createClient } from 'experimental-a2/client'
|
|
|
3
3
|
import { createReact } from 'experimental-a2/react'
|
|
4
4
|
import { appList } from './apps'
|
|
5
5
|
import { crashLog } from './crashes'
|
|
6
|
-
import { appAgent } from './model'
|
|
6
|
+
import { appAgent, type Harness, harnessFrom } from './model'
|
|
7
7
|
|
|
8
8
|
/** The dev tools' switch, `?limit=5s`, for the app at this URL, while they're open. */
|
|
9
9
|
export function shortFunctions(appId: string): boolean {
|
|
@@ -15,17 +15,29 @@ export function shortFunctions(appId: string): boolean {
|
|
|
15
15
|
)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/** The harness the app at this URL runs, from `?harness=`. */
|
|
19
|
+
export function appHarness(appId: string): Harness {
|
|
20
|
+
return location.pathname === `/a0/${appId}`
|
|
21
|
+
? harnessFrom(new URLSearchParams(location.search).get('harness'))
|
|
22
|
+
: 'ai-sdk'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const routes = (appId: string): string => {
|
|
26
|
+
const harness = appHarness(appId)
|
|
27
|
+
return harness === 'ai-sdk' ? '/a0' : `/a0/${harness}`
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
/**
|
|
19
|
-
* Follows an app's conversation log over
|
|
20
|
-
* 5-second function instead while the dev tools' switch is on.
|
|
31
|
+
* Follows an app's conversation log over its harness's events route. Pushes
|
|
32
|
+
* go to a 5-second function instead while the dev tools' switch is on.
|
|
21
33
|
*/
|
|
22
34
|
export const appClient = createClient({
|
|
23
35
|
contract: appAgent,
|
|
24
36
|
api: {
|
|
25
37
|
type: 'http',
|
|
26
|
-
stream:
|
|
38
|
+
stream: (appId) => `${routes(appId)}/events`,
|
|
27
39
|
push: (appId) =>
|
|
28
|
-
shortFunctions(appId) ? '/
|
|
40
|
+
`${routes(appId)}${shortFunctions(appId) ? '/short' : ''}/events`,
|
|
29
41
|
},
|
|
30
42
|
})
|
|
31
43
|
|
|
@@ -4,6 +4,7 @@ import { createContext, useContext, useEffect, useState } from 'react'
|
|
|
4
4
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
|
5
5
|
import { SiteNav } from '../components/site-header'
|
|
6
6
|
import { type App, byLatestPrompt } from './apps'
|
|
7
|
+
import type { Harness } from './model'
|
|
7
8
|
import { AppList } from './app-list'
|
|
8
9
|
import { ColumnEdge, Columns } from './columns'
|
|
9
10
|
import { listCookie, readCookie, type Widths, writeCookie } from './cookies'
|
|
@@ -13,7 +14,7 @@ import { useList } from './session'
|
|
|
13
14
|
type Apps = {
|
|
14
15
|
listed: (id: string) => App | null
|
|
15
16
|
/** Records a prompt to an app, listing it under `name` the first time. */
|
|
16
|
-
prompted: (app: { id: string; name: string }) => void
|
|
17
|
+
prompted: (app: { id: string; name: string; harness: Harness }) => void
|
|
17
18
|
/** Makes a new app on screen the one "+" opens until it's prompted. */
|
|
18
19
|
opened: (appId: string) => void
|
|
19
20
|
}
|
|
@@ -85,12 +86,19 @@ export function A0Shell({
|
|
|
85
86
|
apps.find((app) => app.id === id) ?? null
|
|
86
87
|
const context: Apps = {
|
|
87
88
|
listed,
|
|
88
|
-
prompted: ({ id, name }) => {
|
|
89
|
+
prompted: ({ id, name, harness }) => {
|
|
89
90
|
if (listed(id)) {
|
|
90
91
|
write({ type: 'app.prompted', payload: { appId: id } })
|
|
91
92
|
return
|
|
92
93
|
}
|
|
93
|
-
write({
|
|
94
|
+
write({
|
|
95
|
+
type: 'app.created',
|
|
96
|
+
payload: {
|
|
97
|
+
appId: id,
|
|
98
|
+
name,
|
|
99
|
+
...(harness === 'ai-sdk' ? {} : { harness }),
|
|
100
|
+
},
|
|
101
|
+
})
|
|
94
102
|
if (id === ready) setReady(crypto.randomUUID())
|
|
95
103
|
},
|
|
96
104
|
opened: setReady,
|
|
@@ -19,9 +19,12 @@ const server = () => {
|
|
|
19
19
|
contract: demoAgent,
|
|
20
20
|
store: memory(),
|
|
21
21
|
model: 'test/model',
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
harness: {
|
|
23
|
+
type: 'ai-sdk',
|
|
24
|
+
generate: () => {
|
|
25
|
+
generations += 1
|
|
26
|
+
return new ReadableStream({ start: (controller) => controller.close() })
|
|
27
|
+
},
|
|
25
28
|
},
|
|
26
29
|
})
|
|
27
30
|
return { agentServer, generations: () => generations }
|
|
@@ -137,8 +137,15 @@ export const agentServer = agent({
|
|
|
137
137
|
...(agentScheduler ? { scheduler: agentScheduler } : {}),
|
|
138
138
|
telemetry,
|
|
139
139
|
model: ({ state }) => modelSettings(state.modelChoice),
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
harness: {
|
|
141
|
+
type: 'ai-sdk',
|
|
142
|
+
messageMetadata: traceMetadata,
|
|
143
|
+
compaction: ({ state }) => compactionOptions(state.compactionSettings),
|
|
144
|
+
generation: () => ({
|
|
145
|
+
telemetry: modelTelemetry(),
|
|
146
|
+
toolApproval: { restartService: 'user-approval' },
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
142
149
|
tools,
|
|
143
150
|
instructions: `You are an incident-response agent for the playground.
|
|
144
151
|
When responding to an incident, inspect checkout-api before taking action. Its
|
|
@@ -154,8 +161,4 @@ it again.
|
|
|
154
161
|
|
|
155
162
|
When the operator asks you to run a shell command, call bash. Make one bash call
|
|
156
163
|
at a time and wait for its result before calling it again. Be concise.`,
|
|
157
|
-
generation: () => ({
|
|
158
|
-
telemetry: modelTelemetry(),
|
|
159
|
-
toolApproval: { restartService: 'user-approval' },
|
|
160
|
-
}),
|
|
161
164
|
})
|
|
@@ -11,7 +11,15 @@ import { inputs } from 'experimental-a2/ai'
|
|
|
11
11
|
import { agent } from 'experimental-a2/ai/server'
|
|
12
12
|
import { memory } from 'experimental-a2/store-memory'
|
|
13
13
|
import { otel } from 'experimental-a2/otel'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
afterAll,
|
|
16
|
+
beforeAll,
|
|
17
|
+
beforeEach,
|
|
18
|
+
expect,
|
|
19
|
+
it,
|
|
20
|
+
onTestFinished,
|
|
21
|
+
vi,
|
|
22
|
+
} from 'vitest'
|
|
15
23
|
import { demoAgent } from './model'
|
|
16
24
|
import { modelTelemetry, traceMetadata } from './telemetry'
|
|
17
25
|
import { traceUrl } from './trace'
|
|
@@ -51,60 +59,73 @@ afterAll(async () => {
|
|
|
51
59
|
it.each(['complete', 'stop'] as const)(
|
|
52
60
|
'correlates standard SDK traces and releases spans when generation ends with %s',
|
|
53
61
|
async (outcome) => {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
usage: {
|
|
75
|
-
inputTokens: {
|
|
76
|
-
total: 5,
|
|
77
|
-
noCache: 5,
|
|
78
|
-
cacheRead: 0,
|
|
79
|
-
cacheWrite: 0,
|
|
80
|
-
},
|
|
81
|
-
outputTokens: { total: 2, text: 2, reasoning: 0 },
|
|
82
|
-
},
|
|
83
|
-
providerMetadata: {
|
|
84
|
-
test: { secret: 'PRIVATE_METADATA' },
|
|
62
|
+
const model = new MockLanguageModelV4({
|
|
63
|
+
modelId: 'test-model',
|
|
64
|
+
doStream: async ({ abortSignal }) => ({
|
|
65
|
+
stream:
|
|
66
|
+
outcome === 'complete'
|
|
67
|
+
? simulateReadableStream({
|
|
68
|
+
chunks: [
|
|
69
|
+
{ type: 'stream-start', warnings: [] },
|
|
70
|
+
{ type: 'text-start', id: 'text' },
|
|
71
|
+
{ type: 'text-delta', id: 'text', delta: 'PRIVATE_OUTPUT' },
|
|
72
|
+
{ type: 'text-end', id: 'text' },
|
|
73
|
+
{
|
|
74
|
+
type: 'finish',
|
|
75
|
+
finishReason: { unified: 'stop', raw: 'stop' },
|
|
76
|
+
usage: {
|
|
77
|
+
inputTokens: {
|
|
78
|
+
total: 5,
|
|
79
|
+
noCache: 5,
|
|
80
|
+
cacheRead: 0,
|
|
81
|
+
cacheWrite: 0,
|
|
85
82
|
},
|
|
83
|
+
outputTokens: { total: 2, text: 2, reasoning: 0 },
|
|
84
|
+
},
|
|
85
|
+
providerMetadata: {
|
|
86
|
+
test: { secret: 'PRIVATE_METADATA' },
|
|
86
87
|
},
|
|
87
|
-
],
|
|
88
|
-
})
|
|
89
|
-
: new ReadableStream({
|
|
90
|
-
start(controller) {
|
|
91
|
-
controller.enqueue({ type: 'stream-start', warnings: [] })
|
|
92
|
-
controller.enqueue({ type: 'text-start', id: 'text' })
|
|
93
|
-
controller.enqueue({
|
|
94
|
-
type: 'text-delta',
|
|
95
|
-
id: 'text',
|
|
96
|
-
delta: 'PRIVATE_PARTIAL',
|
|
97
|
-
})
|
|
98
|
-
abortSignal?.addEventListener(
|
|
99
|
-
'abort',
|
|
100
|
-
() => controller.close(),
|
|
101
|
-
{ once: true },
|
|
102
|
-
)
|
|
103
88
|
},
|
|
104
|
-
|
|
105
|
-
|
|
89
|
+
],
|
|
90
|
+
})
|
|
91
|
+
: new ReadableStream({
|
|
92
|
+
start(controller) {
|
|
93
|
+
controller.enqueue({ type: 'stream-start', warnings: [] })
|
|
94
|
+
controller.enqueue({ type: 'text-start', id: 'text' })
|
|
95
|
+
controller.enqueue({
|
|
96
|
+
type: 'text-delta',
|
|
97
|
+
id: 'text',
|
|
98
|
+
delta: 'PRIVATE_PARTIAL',
|
|
99
|
+
})
|
|
100
|
+
abortSignal?.addEventListener(
|
|
101
|
+
'abort',
|
|
102
|
+
() => controller.close(),
|
|
103
|
+
{ once: true },
|
|
104
|
+
)
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
106
107
|
}),
|
|
107
108
|
})
|
|
109
|
+
// Agents take AI Gateway model IDs; the AI SDK's global provider serves
|
|
110
|
+
// this one from the mock.
|
|
111
|
+
vi.stubGlobal('AI_SDK_DEFAULT_PROVIDER', {
|
|
112
|
+
specificationVersion: 'v4',
|
|
113
|
+
languageModel: () => model,
|
|
114
|
+
})
|
|
115
|
+
onTestFinished(() => {
|
|
116
|
+
vi.unstubAllGlobals()
|
|
117
|
+
})
|
|
118
|
+
const server = agent({
|
|
119
|
+
contract: demoAgent,
|
|
120
|
+
store: memory(),
|
|
121
|
+
telemetry: otel(),
|
|
122
|
+
harness: {
|
|
123
|
+
type: 'ai-sdk',
|
|
124
|
+
generation: { telemetry: modelTelemetry() },
|
|
125
|
+
messageMetadata: traceMetadata,
|
|
126
|
+
},
|
|
127
|
+
model: 'test-model',
|
|
128
|
+
})
|
|
108
129
|
const session = server.session(`trace-${outcome}`)
|
|
109
130
|
await session.append(
|
|
110
131
|
...inputs.message({
|
|
@@ -330,7 +330,7 @@ function CanvasClient({
|
|
|
330
330
|
|
|
331
331
|
// A ghost is dead once the log has moved past it: show `dragging`
|
|
332
332
|
// only while its `seen` frontier reaches the shape's durable
|
|
333
|
-
// `movedAt` (/
|
|
333
|
+
// `movedAt` (/client/presence#reconciling-the-planes-seen).
|
|
334
334
|
const ghosts = new Map<
|
|
335
335
|
string,
|
|
336
336
|
{ x: number; y: number; color: PaletteColor }
|
|
@@ -26,32 +26,36 @@ vi.mock('experimental-a2/ai/server', async (importActual) => {
|
|
|
26
26
|
agent: (options: Parameters<typeof actual.agent>[0]) =>
|
|
27
27
|
actual.agent({
|
|
28
28
|
...options,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
messages
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
29
|
+
harness: {
|
|
30
|
+
...(options.harness?.type === 'ai-sdk' ? options.harness : {}),
|
|
31
|
+
type: 'ai-sdk',
|
|
32
|
+
generate: ({
|
|
33
|
+
event,
|
|
34
|
+
messages,
|
|
35
|
+
}: {
|
|
36
|
+
event: { payload: { reason: string } }
|
|
37
|
+
messages: { role: string; id: string }[]
|
|
38
|
+
}) => {
|
|
39
|
+
prompts.push(
|
|
40
|
+
messages.map((message) => `${message.role}:${message.id}`).join(),
|
|
41
|
+
)
|
|
42
|
+
return step(
|
|
43
|
+
event.payload.reason === 'message'
|
|
44
|
+
? [
|
|
45
|
+
{ type: 'start' },
|
|
46
|
+
...['Hello', 'there'].map(
|
|
47
|
+
(message, call): UIMessageChunk => ({
|
|
48
|
+
type: 'tool-input-available',
|
|
49
|
+
toolCallId: `call-${call}`,
|
|
50
|
+
toolName: 'post',
|
|
51
|
+
input: { message },
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
54
|
+
{ type: 'finish', finishReason: 'tool-calls' },
|
|
55
|
+
]
|
|
56
|
+
: [{ type: 'start' }, { type: 'finish', finishReason: 'stop' }],
|
|
57
|
+
)
|
|
58
|
+
},
|
|
55
59
|
},
|
|
56
60
|
} as never),
|
|
57
61
|
}
|
|
@@ -61,57 +61,57 @@ Sound like a sharp, relaxed person in the chat, not a customer-support bot. Matc
|
|
|
61
61
|
Only contribute when you have something useful, relevant, or friendly to add. It is fine to stay silent or stop whenever you have nothing else to say.
|
|
62
62
|
To speak, call post once for each visible chat bubble. Never communicate through ordinary assistant text because it is private and is not shown in the room.
|
|
63
63
|
Use as many post calls as the moment naturally calls for. Several short messages are welcome when they create better rhythm, separate distinct thoughts, or fulfill a request for a sequence. A single message is fine too. Do not spam. After post results, continue only when another message genuinely improves the conversation.`,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
harness: {
|
|
65
|
+
type: 'ai-sdk',
|
|
66
|
+
generation: {
|
|
67
|
+
maxOutputTokens: 500,
|
|
68
|
+
temperature: 0.4,
|
|
69
|
+
},
|
|
70
|
+
messageMetadata: (context) => {
|
|
71
|
+
const previous = context.messages.find(
|
|
72
|
+
(message) => message.id === context.event.payload.responseMessageId,
|
|
73
|
+
)?.metadata
|
|
74
|
+
const trigger = context.messages.find(
|
|
75
|
+
(message) => message.id === context.event.payload.messageId,
|
|
76
|
+
)?.metadata
|
|
77
|
+
const steps = previous?.kind === 'agent' ? previous.steps : []
|
|
78
|
+
const mode: ChatAgentMode | undefined =
|
|
79
|
+
previous?.kind === 'agent'
|
|
80
|
+
? previous.mode
|
|
81
|
+
: trigger?.kind === 'participant' && trigger.mode !== 'passive'
|
|
82
|
+
? trigger.mode
|
|
83
|
+
: undefined
|
|
84
|
+
if (mode === undefined) return undefined
|
|
85
|
+
const metadata = {
|
|
86
|
+
kind: 'agent' as const,
|
|
87
|
+
author: 'agent' as const,
|
|
88
|
+
mode,
|
|
89
|
+
steps,
|
|
90
|
+
}
|
|
91
|
+
if (context.part.type === 'start') return metadata
|
|
92
|
+
if (context.part.type !== 'finish') return undefined
|
|
93
|
+
return {
|
|
94
|
+
...metadata,
|
|
95
|
+
steps: [
|
|
96
|
+
...steps,
|
|
97
|
+
{
|
|
98
|
+
generationId: context.generationId,
|
|
99
|
+
model: context.model,
|
|
100
|
+
finishReason: context.part.finishReason,
|
|
101
|
+
...(context.part.rawFinishReason === undefined
|
|
102
|
+
? {}
|
|
103
|
+
: { rawFinishReason: context.part.rawFinishReason }),
|
|
104
|
+
usage: context.part.totalUsage,
|
|
105
|
+
completedAt: new Date().toISOString(),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
}
|
|
109
|
+
},
|
|
67
110
|
},
|
|
68
111
|
progress: {
|
|
69
112
|
maxChunks: 4,
|
|
70
113
|
maxDelayMs: 30,
|
|
71
114
|
},
|
|
72
|
-
messageMetadata: (context) => {
|
|
73
|
-
const previous = context.messages.find(
|
|
74
|
-
(message) => message.id === context.event.payload.responseMessageId,
|
|
75
|
-
)?.metadata
|
|
76
|
-
const trigger = context.messages.find(
|
|
77
|
-
(message) => message.id === context.event.payload.messageId,
|
|
78
|
-
)?.metadata
|
|
79
|
-
const steps = previous?.kind === 'agent' ? previous.steps : []
|
|
80
|
-
const mode: ChatAgentMode | undefined =
|
|
81
|
-
previous?.kind === 'agent'
|
|
82
|
-
? previous.mode
|
|
83
|
-
: trigger?.kind === 'participant' && trigger.mode !== 'passive'
|
|
84
|
-
? trigger.mode
|
|
85
|
-
: undefined
|
|
86
|
-
if (mode === undefined) return undefined
|
|
87
|
-
const metadata = {
|
|
88
|
-
kind: 'agent' as const,
|
|
89
|
-
author: 'agent' as const,
|
|
90
|
-
mode,
|
|
91
|
-
steps,
|
|
92
|
-
}
|
|
93
|
-
if (context.part.type === 'start') return metadata
|
|
94
|
-
if (context.part.type !== 'finish') return undefined
|
|
95
|
-
return {
|
|
96
|
-
...metadata,
|
|
97
|
-
steps: [
|
|
98
|
-
...steps,
|
|
99
|
-
{
|
|
100
|
-
generationId: context.generationId,
|
|
101
|
-
model:
|
|
102
|
-
typeof context.model === 'string'
|
|
103
|
-
? context.model
|
|
104
|
-
: context.model.modelId,
|
|
105
|
-
finishReason: context.part.finishReason,
|
|
106
|
-
...(context.part.rawFinishReason === undefined
|
|
107
|
-
? {}
|
|
108
|
-
: { rawFinishReason: context.part.rawFinishReason }),
|
|
109
|
-
usage: context.part.totalUsage,
|
|
110
|
-
completedAt: new Date().toISOString(),
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
115
|
})
|
|
116
116
|
|
|
117
117
|
/** The room. Each message goes on to the agent's session, marked for whether it should answer. */
|