experimental-a2 0.20.0 → 0.21.1

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.
Files changed (286) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-C7b-1arn.d.ts → actor-HnGmrOZZ.d.ts} +5 -5
  4. package/dist/{actor-C7b-1arn.d.ts.map → actor-HnGmrOZZ.d.ts.map} +1 -1
  5. package/dist/actor-client.d.ts +1 -1
  6. package/dist/actor-client.js +2 -2
  7. package/dist/actor-react.d.ts +3 -3
  8. package/dist/actor-react.js +5 -5
  9. package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
  10. package/dist/{actor-shared-t7w8xIKP.js.map → actor-shared-DFET5q16.js.map} +1 -1
  11. package/dist/actor.d.ts +1 -1
  12. package/dist/actor.js +54 -43
  13. package/dist/actor.js.map +1 -1
  14. package/dist/{ai-Cz3hqVhk.d.ts → ai-CvEnqVfo.d.ts} +8 -8
  15. package/dist/{ai-Cz3hqVhk.d.ts.map → ai-CvEnqVfo.d.ts.map} +1 -1
  16. package/dist/{ai-mMiWul1T.js → ai-CzE672Q3.js} +9 -9
  17. package/dist/ai-CzE672Q3.js.map +1 -0
  18. package/dist/ai-client.d.ts +2 -2
  19. package/dist/ai-client.js +2 -2
  20. package/dist/ai-fx.d.ts +1 -1
  21. package/dist/ai-fx.js +3 -6
  22. package/dist/ai-fx.js.map +1 -1
  23. package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-Bfk-Lycn.d.ts} +2 -2
  24. package/dist/{ai-harness-CV6rXbB3.d.ts.map → ai-harness-Bfk-Lycn.d.ts.map} +1 -1
  25. package/dist/{ai-harness-Clbg5ei7.js → ai-harness-dQeJaR1t.js} +7 -3
  26. package/dist/ai-harness-dQeJaR1t.js.map +1 -0
  27. package/dist/ai-pi.d.ts +1 -1
  28. package/dist/ai-pi.js +3 -3
  29. package/dist/ai-pi.js.map +1 -1
  30. package/dist/ai-react.d.ts +5 -5
  31. package/dist/ai-react.js +2 -2
  32. package/dist/ai-server.d.ts +14 -17
  33. package/dist/ai-server.d.ts.map +1 -1
  34. package/dist/ai-server.js +80 -73
  35. package/dist/ai-server.js.map +1 -1
  36. package/dist/ai.d.ts +2 -2
  37. package/dist/ai.js +2 -2
  38. package/dist/{client-BhHLoNKF.js → client-9IeTsiAs.js} +5 -5
  39. package/dist/{client-BhHLoNKF.js.map → client-9IeTsiAs.js.map} +1 -1
  40. package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
  41. package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
  42. package/dist/client.d.ts +3 -3
  43. package/dist/client.js +1 -1
  44. package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
  45. package/dist/contract-CWukt01d.js.map +1 -0
  46. package/dist/index.d.ts +5 -5
  47. package/dist/index.js +3 -3
  48. package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
  49. package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
  50. package/dist/{internal-CscSY7K_.js → internal-Bc4PvfHk.js} +10 -3
  51. package/dist/internal-Bc4PvfHk.js.map +1 -0
  52. package/dist/otel.d.ts +1 -1
  53. package/dist/otel.js +1 -1
  54. package/dist/otel.js.map +1 -1
  55. package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
  56. package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
  57. package/dist/react.d.ts +1 -1
  58. package/dist/react.js +1 -1
  59. package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
  60. package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
  61. package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
  62. package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
  63. package/dist/runtime-consumer-BWmht60z.js +12 -0
  64. package/dist/runtime-consumer-BWmht60z.js.map +1 -0
  65. package/dist/scheduler-qstash.d.ts +2 -2
  66. package/dist/scheduler-qstash.js +3 -3
  67. package/dist/scheduler-task-X1SToDjP.js.map +1 -1
  68. package/dist/scheduler-vercel.d.ts +2 -2
  69. package/dist/scheduler-vercel.js +3 -3
  70. package/dist/scheduler-vercel.js.map +1 -1
  71. package/dist/{server-DYAATFuT.d.ts → server-BshFZ0eL.d.ts} +108 -41
  72. package/dist/server-BshFZ0eL.d.ts.map +1 -0
  73. package/dist/{server-qyb5aJJG.js → server-DvIB8kj5.js} +387 -113
  74. package/dist/server-DvIB8kj5.js.map +1 -0
  75. package/dist/server.browser.js +1 -1
  76. package/dist/server.browser.js.map +1 -1
  77. package/dist/server.d.ts +4 -4
  78. package/dist/server.js +2 -2
  79. package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-Bk_29eEw.d.ts} +2 -2
  80. package/dist/session-clear-Bk_29eEw.d.ts.map +1 -0
  81. package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
  82. package/dist/session-clear-wHd5GCpZ.js.map +1 -0
  83. package/dist/store-N8PXxDAS.js.map +1 -1
  84. package/dist/store-Z2qjw50n.d.ts +523 -0
  85. package/dist/store-Z2qjw50n.d.ts.map +1 -0
  86. package/dist/store-codec-DwrWlgQx.js +65 -0
  87. package/dist/store-codec-DwrWlgQx.js.map +1 -0
  88. package/dist/store-memory.d.ts +1 -1
  89. package/dist/store-memory.d.ts.map +1 -1
  90. package/dist/store-memory.js +393 -210
  91. package/dist/store-memory.js.map +1 -1
  92. package/dist/{store-polling-7WJV0YWP.js → store-polling-DYNyxCev.js} +2 -2
  93. package/dist/{store-polling-7WJV0YWP.js.map → store-polling-DYNyxCev.js.map} +1 -1
  94. package/dist/store-postgres.d.ts +1 -1
  95. package/dist/store-postgres.d.ts.map +1 -1
  96. package/dist/store-postgres.js +1586 -566
  97. package/dist/store-postgres.js.map +1 -1
  98. package/dist/{store-presence-polling-B1ixeqIS.js → store-presence-polling-CzQ0wdtM.js} +3 -3
  99. package/dist/{store-presence-polling-B1ixeqIS.js.map → store-presence-polling-CzQ0wdtM.js.map} +1 -1
  100. package/dist/store-redis-http.d.ts +1 -1
  101. package/dist/store-redis-http.js +4 -4
  102. package/dist/store-redis-notify-IiVdlYHc.js +2919 -0
  103. package/dist/store-redis-notify-IiVdlYHc.js.map +1 -0
  104. package/dist/store-redis.d.ts +1 -1
  105. package/dist/store-redis.js +2 -2
  106. package/dist/store-sqlite.d.ts +1 -1
  107. package/dist/store-sqlite.d.ts.map +1 -1
  108. package/dist/store-sqlite.js +517 -287
  109. package/dist/store-sqlite.js.map +1 -1
  110. package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
  111. package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
  112. package/dist/{wire-BKxH1vbP.js → wire-CZGqNZRx.js} +5 -4
  113. package/dist/wire-CZGqNZRx.js.map +1 -0
  114. package/docs/01-quickstart.mdx +21 -15
  115. package/docs/02-examples.mdx +1 -1
  116. package/docs/actors/01-introduction.mdx +8 -0
  117. package/docs/actors/02-concurrency.mdx +1 -1
  118. package/docs/agents/01-introduction.mdx +1 -1
  119. package/docs/agents/02-tools.mdx +12 -11
  120. package/docs/agents/03-conversations.mdx +30 -20
  121. package/docs/agents/04-harnesses.mdx +7 -3
  122. package/docs/client/03-presence.mdx +18 -14
  123. package/docs/contract/01-events.mdx +2 -2
  124. package/docs/contract/02-state.mdx +18 -14
  125. package/docs/contract/03-sessions.mdx +32 -25
  126. package/docs/index.mdx +29 -24
  127. package/docs/reference/01-core.mdx +4 -4
  128. package/docs/reference/02-server.mdx +245 -190
  129. package/docs/reference/04-ai.mdx +20 -19
  130. package/docs/reference/05-telemetry.mdx +27 -13
  131. package/docs/reference/06-errors.mdx +24 -18
  132. package/docs/reference/meta.ts +1 -1
  133. package/docs/schedulers/01-overview.mdx +8 -8
  134. package/docs/schedulers/02-vercel-queues.mdx +2 -2
  135. package/docs/schedulers/03-qstash.mdx +4 -4
  136. package/docs/server/01-consumers.mdx +415 -0
  137. package/docs/server/02-timers.mdx +31 -21
  138. package/docs/server/03-cancellation.mdx +52 -44
  139. package/docs/server/04-routes.mdx +4 -1
  140. package/docs/server/05-sql-projections.mdx +391 -0
  141. package/docs/server/06-guarantees.mdx +55 -48
  142. package/docs/stores/01-overview.mdx +3 -3
  143. package/docs/stores/02-redis.mdx +11 -3
  144. package/docs/stores/03-redis-http.mdx +2 -2
  145. package/docs/stores/04-postgres.mdx +65 -14
  146. package/docs/stores/05-sqlite.mdx +5 -3
  147. package/docs/upgrades/01-overview.mdx +30 -0
  148. package/docs/upgrades/79-0-21.mdx +165 -0
  149. package/docs/upgrades/80-0-20.mdx +43 -0
  150. package/docs/upgrades/81-0-19.mdx +33 -0
  151. package/docs/upgrades/82-0-18.mdx +33 -0
  152. package/docs/upgrades/83-0-17.mdx +69 -0
  153. package/docs/upgrades/meta.ts +3 -0
  154. package/examples/playground/DEPLOY.md +39 -7
  155. package/examples/playground/app/a0/chat.test.tsx +2 -2
  156. package/examples/playground/app/a0/sandbox-log.ts +17 -15
  157. package/examples/playground/app/a0/server.ts +4 -4
  158. package/examples/playground/app/a2slack/a2slack.css +1067 -0
  159. package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
  160. package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
  161. package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
  162. package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
  163. package/examples/playground/app/a2slack/consumers/search.ts +37 -0
  164. package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
  165. package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
  166. package/examples/playground/app/a2slack/db/a2.ts +206 -0
  167. package/examples/playground/app/a2slack/db/index.ts +73 -0
  168. package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
  169. package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
  170. package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
  171. package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
  172. package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
  173. package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
  174. package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
  175. package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
  176. package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +34 -0
  177. package/examples/playground/app/a2slack/db/queries.ts +263 -0
  178. package/examples/playground/app/a2slack/db/schema.ts +145 -0
  179. package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
  180. package/examples/playground/app/a2slack/drills/harness.ts +244 -0
  181. package/examples/playground/app/a2slack/events/route.ts +36 -0
  182. package/examples/playground/app/a2slack/identity.ts +44 -0
  183. package/examples/playground/app/a2slack/inbox/route.ts +8 -0
  184. package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
  185. package/examples/playground/app/a2slack/lag/page.tsx +101 -0
  186. package/examples/playground/app/a2slack/lag.ts +73 -0
  187. package/examples/playground/app/a2slack/layout.tsx +43 -0
  188. package/examples/playground/app/a2slack/messages/route.ts +17 -0
  189. package/examples/playground/app/a2slack/model.test.ts +208 -0
  190. package/examples/playground/app/a2slack/model.ts +369 -0
  191. package/examples/playground/app/a2slack/models.test.ts +72 -0
  192. package/examples/playground/app/a2slack/page.tsx +10 -0
  193. package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
  194. package/examples/playground/app/a2slack/search/route.ts +40 -0
  195. package/examples/playground/app/a2slack/search-index.ts +108 -0
  196. package/examples/playground/app/a2slack/server.test.ts +315 -0
  197. package/examples/playground/app/a2slack/server.ts +55 -0
  198. package/examples/playground/app/a2slack/sessions.ts +50 -0
  199. package/examples/playground/app/a2slack/thread/route.ts +8 -0
  200. package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
  201. package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
  202. package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
  203. package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
  204. package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
  205. package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
  206. package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
  207. package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
  208. package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
  209. package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
  210. package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
  211. package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
  212. package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
  213. package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
  214. package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
  215. package/examples/playground/app/agent/server.ts +3 -3
  216. package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
  217. package/examples/playground/app/chat/server.ts +45 -35
  218. package/examples/playground/app/components/demo-icons.tsx +7 -0
  219. package/examples/playground/app/components/demos.tsx +7 -0
  220. package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
  221. package/examples/playground/app/orders/page.tsx +1 -1
  222. package/examples/playground/app/orders/server.ts +49 -30
  223. package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
  224. package/examples/playground/app/recovery/page.tsx +4 -4
  225. package/examples/playground/app/recovery/server.ts +23 -32
  226. package/examples/playground/drizzle.config.ts +14 -0
  227. package/examples/playground/next.config.ts +6 -1
  228. package/examples/playground/package.json +12 -1
  229. package/examples/playground/scripts/migrate.mjs +31 -0
  230. package/examples/playground/vercel.json +9 -0
  231. package/examples/playground/vitest.drills.config.ts +32 -0
  232. package/package.json +5 -2
  233. package/sql/postgres/drizzle-schema.ts +206 -0
  234. package/sql/postgres/schema.sql +206 -67
  235. package/src/actor.ts +101 -71
  236. package/src/ai-agent.ts +149 -111
  237. package/src/ai-control-server.ts +8 -8
  238. package/src/ai-fx.ts +4 -6
  239. package/src/ai-harness.ts +10 -2
  240. package/src/ai-pi.ts +2 -2
  241. package/src/ai-server.ts +2 -2
  242. package/src/ai.ts +7 -7
  243. package/src/contract.ts +1 -1
  244. package/src/deterministic-id.ts +26 -3
  245. package/src/errors.ts +8 -5
  246. package/src/idempotent-replay.ts +0 -1
  247. package/src/index.ts +1 -1
  248. package/src/internal.ts +15 -5
  249. package/src/otel.ts +1 -1
  250. package/src/postgres-schema.ts +98 -57
  251. package/src/push-envelope.ts +20 -0
  252. package/src/runtime-consumer.ts +10 -0
  253. package/src/scheduler-task.ts +2 -2
  254. package/src/scheduler-vercel.ts +1 -1
  255. package/src/server.browser.ts +1 -1
  256. package/src/server.ts +774 -207
  257. package/src/session-socket.ts +1 -1
  258. package/src/store-codec.ts +66 -0
  259. package/src/store-memory.ts +581 -255
  260. package/src/store-postgres.ts +1984 -825
  261. package/src/store-redis-core.ts +1624 -1152
  262. package/src/store-redis-notify.ts +2 -0
  263. package/src/store-sqlite.ts +891 -486
  264. package/src/store.ts +227 -97
  265. package/src/telemetry.ts +2 -1
  266. package/src/wire.ts +1 -0
  267. package/dist/ai-harness-Clbg5ei7.js.map +0 -1
  268. package/dist/ai-mMiWul1T.js.map +0 -1
  269. package/dist/contract-BYKASlCV.js.map +0 -1
  270. package/dist/idempotent-replay-JdgMQRRm.js +0 -21
  271. package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
  272. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
  273. package/dist/internal-CscSY7K_.js.map +0 -1
  274. package/dist/server-DYAATFuT.d.ts.map +0 -1
  275. package/dist/server-qyb5aJJG.js.map +0 -1
  276. package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
  277. package/dist/session-clear-DGI4jTQ5.js.map +0 -1
  278. package/dist/store-CyLUy86v.d.ts +0 -404
  279. package/dist/store-CyLUy86v.d.ts.map +0 -1
  280. package/dist/store-codec-DTG0Ftek.js +0 -10
  281. package/dist/store-codec-DTG0Ftek.js.map +0 -1
  282. package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
  283. package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
  284. package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
  285. package/dist/wire-BKxH1vbP.js.map +0 -1
  286. package/docs/server/01-handlers.mdx +0 -227
package/src/ai-agent.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * The server-only implementation of the experimental-a2/ai protocol: the
3
- * handler table and `agent()`, its assembly as an ordinary A2 server.
2
+ * The server-only implementation of the experimental-a2/ai protocol: its
3
+ * consumers and `agent()`, its assembly as an ordinary A2 server.
4
4
  */
5
5
 
6
6
  // oxlint-disable no-await-in-loop -- stream chunks and durable appends are
@@ -79,10 +79,12 @@ import {
79
79
  installAmbientToolScopeStorage,
80
80
  type AmbientToolScope,
81
81
  } from './internal.ts'
82
+ import { runtimeConsumer } from './runtime-consumer.ts'
82
83
  import {
83
84
  createServer,
84
85
  type A2Server,
85
- type HandlerContext,
86
+ type Consumer,
87
+ type ConsumerContext,
86
88
  type ServerOptions,
87
89
  } from './server.ts'
88
90
  import {
@@ -191,7 +193,7 @@ export type AgentResolverContext<
191
193
  * in the checkpoint the generation already reads, so it costs no I/O.
192
194
  */
193
195
  state: X
194
- session: Pick<HandlerContext<D>['session'], 'state'>
196
+ session: Pick<ConsumerContext<D>['session'], 'state'>
195
197
  signal: AbortSignal
196
198
  }
197
199
 
@@ -239,7 +241,7 @@ export type CompactionOptions<
239
241
 
240
242
  export type AgentGenerationSettings<T extends ToolSet> = AISDKStepSettings<T>
241
243
 
242
- export type HandlersOptions<
244
+ export type ConsumersOptions<
243
245
  M extends UIMessage,
244
246
  D extends AIEventDefs<M> & EventDefs,
245
247
  T extends ToolSet = ToolSet,
@@ -344,27 +346,24 @@ type AgentServerOptions<
344
346
  T extends ToolSet,
345
347
  X extends object = Record<never, never>,
346
348
  H extends string = string,
349
+ C extends readonly unknown[] = readonly Consumer<D>[],
347
350
  > = AgentModelOptions<M, D, T, X, H> &
348
- Omit<
349
- ServerOptions<D, Record<never, never>, Reducer<D, S>>,
350
- 'contract' | 'handlers'
351
- > & {
352
- /** Handlers for application events. */
353
- handlers?: ServerOptions<D, Record<never, never>, Reducer<D, S>>['handlers']
354
- }
351
+ Omit<ServerOptions<D, Record<never, never>, Reducer<D, S>, C>, 'contract'>
355
352
 
356
353
  export type AgentOptions<
357
354
  M extends UIMessage,
358
355
  E extends EventDefs,
359
356
  T extends ToolSet,
360
357
  H extends string = string,
358
+ C extends readonly unknown[] = readonly Consumer<AIEventDefs<M> & E>[],
361
359
  > = AgentServerOptions<
362
360
  NoInfer<M>,
363
361
  AIEventDefs<NoInfer<M>> & E,
364
362
  AIState<NoInfer<M>>,
365
363
  T,
366
364
  Record<never, never>,
367
- H
365
+ H,
366
+ C
368
367
  > & {
369
368
  /** Identity: prefixes storage keys and queue messages. */
370
369
  name: string
@@ -384,7 +383,8 @@ export type AgentFromContractOptions<
384
383
  Name extends string = string,
385
384
  X extends object = Record<never, never>,
386
385
  H extends string = string,
387
- > = AgentServerOptions<AIMessageOf<D>, D, S, T, X, H> & {
386
+ C extends readonly unknown[] = readonly Consumer<D>[],
387
+ > = AgentServerOptions<AIMessageOf<D>, D, S, T, X, H, C> & {
388
388
  /**
389
389
  * A shared `agentContract()`. Its application state reaches every resolver
390
390
  * as `state`; a fully custom contract is core A2, served by `createServer()`.
@@ -480,8 +480,8 @@ const toolScopeStorage = installAmbientToolScopeStorage(
480
480
  () => new AsyncLocalStorage<AmbientToolScope>(),
481
481
  )
482
482
 
483
- /** Run `fn` with `ctx` as the ambient tool handler context, for direct tool tests. */
484
- export function runWithHandlerContext<D extends AgentToolEventDefs, R>(
483
+ /** Run `fn` with `ctx` as the ambient tool consumer context, for direct tool tests. */
484
+ export function runWithConsumerContext<D extends AgentToolEventDefs, R>(
485
485
  ctx: AgentToolContext<D>,
486
486
  fn: () => R,
487
487
  ): R {
@@ -683,7 +683,7 @@ async function* consumeGeneration(options: {
683
683
  const reader = options.source.stream.getReader()
684
684
  // The next read starts before a batch is yielded, and the consumer may
685
685
  // take a while to ask again: a stream that fails meanwhile must not leave
686
- // that read rejected with no handler, which ends the process.
686
+ // that read rejected with nothing to catch it, which ends the process.
687
687
  const readAhead = (): Promise<StreamRead<UIMessageChunk>> => {
688
688
  const read = reader.read()
689
689
  read.catch(() => {})
@@ -1232,14 +1232,12 @@ const assertNoLoopOptions = (options: { harness?: { type: string } }): void => {
1232
1232
  }
1233
1233
  }
1234
1234
 
1235
- export function createHandlers<
1235
+ export function createConsumers<
1236
1236
  M extends UIMessage,
1237
1237
  D extends AIEventDefs<M> & EventDefs,
1238
1238
  T extends ToolSet = ToolSet,
1239
1239
  X extends object = Record<never, never>,
1240
- >(
1241
- options: HandlersOptions<M, D, T, X>,
1242
- ): NonNullable<ServerOptions<D>['handlers']> {
1240
+ >(options: ConsumersOptions<M, D, T, X>): Consumer<D>[] {
1243
1241
  if (options.model === undefined) {
1244
1242
  throw new TypeError('agent requires model')
1245
1243
  }
@@ -1349,7 +1347,7 @@ export function createHandlers<
1349
1347
  contract: options.contract,
1350
1348
  })
1351
1349
  const readContext = async (
1352
- session: HandlerContext<D>['session'],
1350
+ session: ConsumerContext<D>['session'],
1353
1351
  through: number | 'latest',
1354
1352
  ) => {
1355
1353
  const snapshot = await session.state(contextReducer, { through })
@@ -1390,7 +1388,7 @@ export function createHandlers<
1390
1388
  ) => unknown
1391
1389
  }
1392
1390
 
1393
- type ToolHandlerContext = HandlerContext<D, 'ai.tool.execution.requested'>
1391
+ type ToolConsumerContext = ConsumerContext<D, 'ai.tool.execution.requested'>
1394
1392
 
1395
1393
  const resultEvent = async (
1396
1394
  call: ToolCalledPayload,
@@ -1435,7 +1433,7 @@ export function createHandlers<
1435
1433
  }) as AppendInput<D>
1436
1434
 
1437
1435
  const promptMessages = (input: {
1438
- ctx: ToolHandlerContext
1436
+ ctx: ToolConsumerContext
1439
1437
  call: ToolCalledPayload
1440
1438
  generation: GenerationStartedPayload
1441
1439
  }): Promise<ModelMessage[]> => {
@@ -1485,7 +1483,7 @@ export function createHandlers<
1485
1483
  }
1486
1484
 
1487
1485
  const toolExecutionFailure = async (
1488
- ctx: ToolHandlerContext,
1486
+ ctx: ToolConsumerContext,
1489
1487
  call: ToolCalledPayload,
1490
1488
  error: unknown,
1491
1489
  ): Promise<AppendInput<D> | void> => {
@@ -1498,7 +1496,7 @@ export function createHandlers<
1498
1496
  }
1499
1497
 
1500
1498
  const runToolExecution = async (
1501
- ctx: ToolHandlerContext,
1499
+ ctx: ToolConsumerContext,
1502
1500
  call: ToolCalledPayload,
1503
1501
  execute: NonNullable<RuntimeTool['execute']>,
1504
1502
  messages: ModelMessage[],
@@ -1575,7 +1573,7 @@ export function createHandlers<
1575
1573
  }
1576
1574
 
1577
1575
  const executeTool = async (input: {
1578
- ctx: ToolHandlerContext
1576
+ ctx: ToolConsumerContext
1579
1577
  call: ToolCalledPayload
1580
1578
  generation: GenerationStartedPayload
1581
1579
  }): Promise<AppendInput<D> | void> => {
@@ -1629,7 +1627,7 @@ export function createHandlers<
1629
1627
  }
1630
1628
 
1631
1629
  const handleToolExecution = async (
1632
- ctx: ToolHandlerContext,
1630
+ ctx: ToolConsumerContext,
1633
1631
  ): Promise<AppendInput<D>> => {
1634
1632
  try {
1635
1633
  const state = (
@@ -1666,7 +1664,7 @@ export function createHandlers<
1666
1664
  }
1667
1665
 
1668
1666
  const generationHandler = async (
1669
- ctx: HandlerContext<D, 'ai.generation.requested'>,
1667
+ ctx: ConsumerContext<D, 'ai.generation.requested'>,
1670
1668
  ): Promise<void | AppendInput<D> | readonly AppendInput<D>[]> => {
1671
1669
  if (checkAbort(ctx.signal)) return
1672
1670
  const requestId = ctx.event.id
@@ -2315,23 +2313,41 @@ export function createHandlers<
2315
2313
  }
2316
2314
  }
2317
2315
 
2318
- return {
2319
- 'ai.model.metadata.requested': {
2320
- handler: async (ctx) => {
2321
- const limits = await readModelLimits({
2322
- modelId: ctx.event.payload.modelId,
2323
- signal: ctx.signal,
2324
- })
2325
- ctx.signal.throwIfAborted()
2326
- return {
2327
- type: 'ai.model.metadata.resolved',
2328
- id: await aiEventId('a2.ai.model.metadata.resolved', ctx.event.id),
2329
- payload: { modelId: ctx.event.payload.modelId, limits },
2330
- } as AppendInput<D>
2331
- },
2332
- },
2333
- 'ai.control.requested': {
2316
+ const contract = options.contract as unknown as Contract<
2317
+ D,
2318
+ Record<never, never>,
2319
+ undefined
2320
+ >
2321
+ const { toolLane } = options
2322
+ type Of<K extends string> = ContractEvent<D, K & keyof D & string>
2323
+ type Generation = Of<'ai.generation.requested'>
2324
+ type ToolCall = Of<'ai.tool.execution.requested'>
2325
+ return [
2326
+ runtimeConsumer({
2327
+ contract,
2328
+ name: 'a2.ai.metadata',
2329
+ types: ['ai.model.metadata.requested'],
2330
+ }).run(async (ctx) => {
2331
+ const limits = await readModelLimits({
2332
+ modelId: ctx.event.payload.modelId,
2333
+ signal: ctx.signal,
2334
+ })
2335
+ ctx.signal.throwIfAborted()
2336
+ return {
2337
+ type: 'ai.model.metadata.resolved',
2338
+ id: await aiEventId('a2.ai.model.metadata.resolved', ctx.event.id),
2339
+ payload: { modelId: ctx.event.payload.modelId, limits },
2340
+ } as AppendInput<D>
2341
+ }),
2342
+ // One lane for every control event: commands, work reports, and closes
2343
+ // apply one at a time. Its lane function also rejects a bad command at
2344
+ // append, before anything is written.
2345
+ runtimeConsumer({
2346
+ contract,
2347
+ name: 'a2.ai.control',
2348
+ types: ['ai.control.requested', 'ai.work.reported', 'ai.session.closed'],
2334
2349
  lane: ({ event }) => {
2350
+ if (event.type !== 'ai.control.requested') return 'a2.ai.control'
2335
2351
  const command = event.payload as ControlCommand<M>
2336
2352
  if (event.id === '')
2337
2353
  throw new A2Error(
@@ -2353,17 +2369,25 @@ export function createHandlers<
2353
2369
  )
2354
2370
  return 'a2.ai.control'
2355
2371
  },
2356
- handler: controlHandler,
2357
- },
2358
- 'ai.work.reported': { lane: 'a2.ai.control', handler: controlHandler },
2359
- 'ai.session.closed': { lane: 'a2.ai.control', handler: controlHandler },
2360
- 'ai.generation.requested': {
2361
- lane: 'a2.ai.model',
2372
+ }).run(controlHandler as never),
2373
+ runtimeConsumer({
2374
+ contract,
2375
+ name: 'a2.ai.model',
2376
+ types: ['ai.generation.requested'],
2377
+ lane: () => 'a2.ai.model',
2378
+ // The contract is generic here, so the map's keys widen to string and
2379
+ // each predicate names its own event types.
2362
2380
  abortOn: {
2363
- 'ai.control.requested': (event, trigger) =>
2381
+ 'ai.control.requested': (
2382
+ event: Of<'ai.control.requested'>,
2383
+ trigger: Generation,
2384
+ ) =>
2364
2385
  event.payload.action === 'stop' &&
2365
2386
  event.payload.turnId === trigger.payload.control?.turnId,
2366
- 'ai.control.committed': (event, trigger) => {
2387
+ 'ai.control.committed': (
2388
+ event: Of<'ai.control.committed'>,
2389
+ trigger: Generation,
2390
+ ) => {
2367
2391
  const active = (event.payload as ControlCommit).view
2368
2392
  return (
2369
2393
  active?.turnId !== trigger.payload.control?.turnId ||
@@ -2371,39 +2395,50 @@ export function createHandlers<
2371
2395
  )
2372
2396
  },
2373
2397
  'ai.session.closed': true,
2374
- },
2375
- handler: (ctx) =>
2376
- telemetry.span(
2377
- 'a2.ai.generate',
2378
- {
2379
- 'a2.contract': options.contract.name,
2380
- 'a2.session_id': ctx.event.sessionId,
2381
- 'a2.ai.generate.request_id': ctx.event.id,
2382
- 'a2.ai.generate.attempt': ctx.attempt,
2383
- },
2384
- async () => {
2385
- try {
2386
- return control.settled({
2387
- ctx,
2388
- events: await generationHandler(ctx),
2389
- })
2390
- } catch (error) {
2391
- if (control.cancelled({ error, signal: ctx.signal }))
2392
- return control.settled({ ctx, events: undefined })
2393
- throw error
2394
- }
2395
- },
2396
- ),
2397
- },
2398
- 'ai.tool.execution.requested': {
2399
- ...(options.toolLane === undefined ? {} : { lane: options.toolLane }),
2398
+ } as never,
2399
+ }).run((ctx) =>
2400
+ telemetry.span(
2401
+ 'a2.ai.generate',
2402
+ {
2403
+ 'a2.contract': options.contract.name,
2404
+ 'a2.session_id': ctx.event.sessionId,
2405
+ 'a2.ai.generate.request_id': ctx.event.id,
2406
+ 'a2.ai.generate.attempt': ctx.attempt,
2407
+ },
2408
+ async () => {
2409
+ try {
2410
+ return control.settled({
2411
+ ctx,
2412
+ events: await generationHandler(ctx),
2413
+ })
2414
+ } catch (error) {
2415
+ if (control.cancelled({ error, signal: ctx.signal }))
2416
+ return control.settled({ ctx, events: undefined })
2417
+ throw error
2418
+ }
2419
+ },
2420
+ ),
2421
+ ),
2422
+ runtimeConsumer({
2423
+ contract,
2424
+ name: 'a2.ai.tools',
2425
+ types: ['ai.tool.execution.requested'],
2426
+ ...(toolLane === undefined ? {} : { lane: () => toolLane }),
2400
2427
  abortOn: {
2401
- 'ai.control.requested': (event, trigger) =>
2428
+ 'ai.control.requested': (
2429
+ event: Of<'ai.control.requested'>,
2430
+ trigger: ToolCall,
2431
+ ) =>
2402
2432
  event.payload.action === 'stop' &&
2403
2433
  event.payload.turnId === trigger.payload.turnId,
2404
- 'ai.generation.failed': (event, trigger) =>
2405
- event.payload.generationId === trigger.payload.call.generationId,
2406
- 'ai.control.committed': (event, trigger) => {
2434
+ 'ai.generation.failed': (
2435
+ event: Of<'ai.generation.failed'>,
2436
+ trigger: ToolCall,
2437
+ ) => event.payload.generationId === trigger.payload.call.generationId,
2438
+ 'ai.control.committed': (
2439
+ event: Of<'ai.control.committed'>,
2440
+ trigger: ToolCall,
2441
+ ) => {
2407
2442
  const active = (event.payload as ControlCommit).view
2408
2443
  return (
2409
2444
  active?.turnId !== trigger.payload.turnId ||
@@ -2411,26 +2446,26 @@ export function createHandlers<
2411
2446
  )
2412
2447
  },
2413
2448
  'ai.session.closed': true,
2414
- },
2415
- handler: (ctx) =>
2416
- telemetry.span(
2417
- 'a2.ai.tool',
2418
- {
2419
- 'a2.contract': options.contract.name,
2420
- 'a2.session_id': ctx.event.sessionId,
2421
- 'a2.ai.tool.request_id': ctx.event.payload.call.requestId,
2422
- 'a2.ai.tool.call_id': ctx.event.payload.call.toolCallId,
2423
- 'a2.ai.tool.name': ctx.event.payload.call.toolName,
2424
- 'a2.ai.tool.attempt': ctx.attempt,
2425
- },
2426
- () => handleToolExecution(ctx),
2427
- ),
2428
- },
2429
- } as NonNullable<ServerOptions<D>['handlers']>
2449
+ } as never,
2450
+ }).run((ctx) =>
2451
+ telemetry.span(
2452
+ 'a2.ai.tool',
2453
+ {
2454
+ 'a2.contract': options.contract.name,
2455
+ 'a2.session_id': ctx.event.sessionId,
2456
+ 'a2.ai.tool.request_id': ctx.event.payload.call.requestId,
2457
+ 'a2.ai.tool.call_id': ctx.event.payload.call.toolCallId,
2458
+ 'a2.ai.tool.name': ctx.event.payload.call.toolName,
2459
+ 'a2.ai.tool.attempt': ctx.attempt,
2460
+ },
2461
+ () => handleToolExecution(ctx),
2462
+ ),
2463
+ ),
2464
+ ] as Consumer<D>[]
2430
2465
  }
2431
2466
 
2432
2467
  /**
2433
- * Serve an AI agent: the built-in protocol, its handlers, and A2's store,
2468
+ * Serve an AI agent: the built-in protocol, its consumers, and A2's store,
2434
2469
  * scheduler, and telemetry, as one ordinary A2 server. Pass `name` for the
2435
2470
  * built-in protocol, or a shared `agentContract()` when clients fold
2436
2471
  * application state.
@@ -2440,8 +2475,9 @@ export function agent<
2440
2475
  M extends UIMessage = UIMessage,
2441
2476
  E extends EventDefs = Record<never, never>,
2442
2477
  H extends string = never,
2478
+ const C extends readonly unknown[] = readonly Consumer<AIEventDefs<M> & E>[],
2443
2479
  >(
2444
- options: AgentOptions<M, E, T, H>,
2480
+ options: AgentOptions<M, E, T, H, C>,
2445
2481
  ): Agent<AIEventDefs<M> & E, AIState<M>, typeof AI_STATE_REDUCER>
2446
2482
  export function agent<
2447
2483
  D extends AIEventDefs<AIMessageOf<D>> & EventDefs,
@@ -2450,7 +2486,8 @@ export function agent<
2450
2486
  Name extends string = string,
2451
2487
  X extends object = Record<never, never>,
2452
2488
  H extends string = never,
2453
- >(options: AgentFromContractOptions<D, S, T, Name, X, H>): Agent<D, S, Name>
2489
+ const C extends readonly unknown[] = readonly Consumer<D>[],
2490
+ >(options: AgentFromContractOptions<D, S, T, Name, X, H, C>): Agent<D, S, Name>
2454
2491
  export function agent(
2455
2492
  options:
2456
2493
  | AgentOptions<UIMessage, EventDefs, ToolSet>
@@ -2458,6 +2495,12 @@ export function agent(
2458
2495
  ): Agent<AIEventDefs & EventDefs, AIState> {
2459
2496
  type D = AIEventDefs & EventDefs
2460
2497
  assertNoLoopOptions(options)
2498
+ if (
2499
+ options.toolLane !== undefined &&
2500
+ (typeof options.toolLane !== 'string' || options.toolLane.length === 0)
2501
+ ) {
2502
+ throw new TypeError('toolLane must be a non-empty string')
2503
+ }
2461
2504
  const {
2462
2505
  model,
2463
2506
  tools,
@@ -2467,13 +2510,12 @@ export function agent(
2467
2510
  toolRecovery,
2468
2511
  progress,
2469
2512
  toolLane,
2470
- handlers,
2471
2513
  ...rest
2472
2514
  } = options
2473
2515
  let contract: Contract<D, Record<never, never>, Reducer<D, AIState>>
2474
2516
  let serverOptions: Omit<
2475
2517
  ServerOptions<D, Record<never, never>, Reducer<D, AIState>>,
2476
- 'contract' | 'handlers'
2518
+ 'contract'
2477
2519
  >
2478
2520
  if ('contract' in rest) {
2479
2521
  const { contract: shared, ...remaining } = rest
@@ -2493,7 +2535,7 @@ export function agent(
2493
2535
  serverOptions = remaining
2494
2536
  }
2495
2537
  const states = harnessStates()
2496
- const builtIns = createHandlers<UIMessage, D>({
2538
+ const builtIns = createConsumers<UIMessage, D>({
2497
2539
  contract,
2498
2540
  model,
2499
2541
  harnessStates: states,
@@ -2507,15 +2549,11 @@ export function agent(
2507
2549
  ...(toolRecovery === undefined ? {} : { toolRecovery }),
2508
2550
  ...(progress === undefined ? {} : { progress }),
2509
2551
  ...(toolLane === undefined ? {} : { toolLane }),
2510
- } as HandlersOptions<UIMessage, D>)
2511
- for (const type of Object.keys(handlers ?? {})) {
2512
- if (Object.hasOwn(builtIns, type))
2513
- throw new TypeError(`agent handles '${type}' itself`)
2514
- }
2552
+ } as ConsumersOptions<UIMessage, D>)
2515
2553
  const server = createServer({
2516
2554
  ...serverOptions,
2517
2555
  contract,
2518
- handlers: { ...builtIns, ...handlers },
2556
+ consumers: [...builtIns, ...(serverOptions.consumers ?? [])],
2519
2557
  })
2520
2558
  registerHarnessStates(server, states)
2521
2559
  const applicationEvents = 'contract' in rest
@@ -31,7 +31,7 @@ import type {
31
31
  ContractEvent,
32
32
  EventDefs,
33
33
  } from './contract.ts'
34
- import type { HandlerContext } from './server.ts'
34
+ import type { ConsumerContext } from './server.ts'
35
35
  import type { Reducer } from './reducer.ts'
36
36
  import type { AICoordinatorState, CoordinatorCall } from './ai-coordinator.ts'
37
37
  import { validateSync } from './validate.ts'
@@ -56,18 +56,18 @@ export type AIControlRuntime<
56
56
  D extends AIEventDefs<M> & EventDefs,
57
57
  > = {
58
58
  handler(
59
- ctx: HandlerContext<
59
+ ctx: ConsumerContext<
60
60
  D,
61
61
  'ai.control.requested' | 'ai.work.reported' | 'ai.session.closed'
62
62
  >,
63
63
  ): Promise<AppendInput<D>[]>
64
64
  append(options: {
65
- ctx: HandlerContext<D>
65
+ ctx: ConsumerContext<D>
66
66
  name: string
67
67
  events: AppendInput<D>[]
68
68
  }): Promise<ContractEvent<D>[]>
69
69
  settled(options: {
70
- ctx: HandlerContext<D>
70
+ ctx: ConsumerContext<D>
71
71
  events: void | AppendInput<D> | readonly AppendInput<D>[]
72
72
  }): Promise<AppendInput<D>>
73
73
  cancelled(options: { error: unknown; signal: AbortSignal }): boolean
@@ -117,7 +117,7 @@ export const createControlRuntime = <
117
117
  const coordinator = aiCoordinatorReducer(options.contract)
118
118
 
119
119
  const handler = async (
120
- ctx: HandlerContext<
120
+ ctx: ConsumerContext<
121
121
  D,
122
122
  'ai.control.requested' | 'ai.work.reported' | 'ai.session.closed'
123
123
  >,
@@ -761,7 +761,7 @@ export const createControlRuntime = <
761
761
  settled,
762
762
  name,
763
763
  }: {
764
- ctx: HandlerContext<D>
764
+ ctx: ConsumerContext<D>
765
765
  events: readonly AppendInput<D>[]
766
766
  settled: boolean
767
767
  name: string
@@ -828,7 +828,7 @@ export const createControlRuntime = <
828
828
  name,
829
829
  events,
830
830
  }: {
831
- ctx: HandlerContext<D>
831
+ ctx: ConsumerContext<D>
832
832
  name: string
833
833
  events: AppendInput<D>[]
834
834
  }): Promise<ContractEvent<D>[]> => {
@@ -896,7 +896,7 @@ export const createControlRuntime = <
896
896
  ctx,
897
897
  events,
898
898
  }: {
899
- ctx: HandlerContext<D>
899
+ ctx: ConsumerContext<D>
900
900
  events: void | AppendInput<D> | readonly AppendInput<D>[]
901
901
  }): Promise<AppendInput<D>> =>
902
902
  report({
package/src/ai-fx.ts CHANGED
@@ -175,7 +175,7 @@ async function runStep(step: HarnessStep): Promise<HarnessGeneration> {
175
175
  if (outcome.result.stopReason === 'end_turn' && !step.signal.aborted)
176
176
  await saveCheckpoint()
177
177
  } else {
178
- // FX now waits on tools that A2's tool handlers run.
178
+ // FX now waits on tools that A2's tool consumer run.
179
179
  fxTurn.cancel()
180
180
  }
181
181
  controller.enqueue({ type: 'finish-step' })
@@ -417,14 +417,12 @@ async function turnPlan(
417
417
  ): Promise<TurnPlan> {
418
418
  const calls: RecordedCall[] = []
419
419
  const steers = new Map<number, string[]>()
420
- const steer = (at: number, inputs: HarnessInput[] | undefined) => {
421
- if (inputs !== undefined) steers.set(Math.max(0, at), inputs.map(steerText))
422
- }
423
- steer(0, turn.generations.length === 0 ? turn.steers.get(0) : undefined)
424
420
  turn.generations.forEach((generation, index) => {
425
421
  calls.push(...recordedCalls(generation.chunks, generation.completed))
422
+ const inputs = turn.steers.get(index)
426
423
  // A generation's step is its last call.
427
- steer(calls.length - 1, turn.steers.get(index))
424
+ if (inputs !== undefined)
425
+ steers.set(calls.length - 1, inputs.map(steerText))
428
426
  })
429
427
  const images = await harnessImages({
430
428
  harness: 'fx()',
package/src/ai-harness.ts CHANGED
@@ -323,6 +323,7 @@ export type HarnessInput = { text: string; files: FileUIPart[] }
323
323
 
324
324
  /** One harness turn to rebuild: its prompt, completed generations, and end. */
325
325
  export type HarnessTurn = {
326
+ /** With any steering that entered before the turn's first completed generation. */
326
327
  prompt: HarnessInput
327
328
  generations: RecordedGeneration[]
328
329
  /** Steering that entered after the generation at that index. */
@@ -441,8 +442,15 @@ export function harnessTurns(
441
442
  files: response.prompt.flatMap((prompt) => prompt.files),
442
443
  }
443
444
  let turn = turns.at(-1)
444
- if (steered && turn !== undefined) {
445
- const at = Math.max(0, turn.generations.length - 1)
445
+ if (steered && turn?.generations.length === 0) {
446
+ // Nothing answered the prompt yet, so the steering joins it, as messages
447
+ // queued before a response join its prompt.
448
+ turn.prompt = {
449
+ text: `${turn.prompt.text}\n\n${input.text}`,
450
+ files: [...turn.prompt.files, ...input.files],
451
+ }
452
+ } else if (steered && turn !== undefined) {
453
+ const at = turn.generations.length - 1
446
454
  turn.steers.set(at, [...(turn.steers.get(at) ?? []), input])
447
455
  } else {
448
456
  turn = { prompt: input, generations: [], steers: new Map(), end: 'live' }
package/src/ai-pi.ts CHANGED
@@ -436,7 +436,7 @@ async function runStep(
436
436
  const result = results.get(toolCallId)
437
437
  if (result !== undefined) return answeredTool(result, shape)
438
438
  if (state.plan.end === 'live' && state.liveStep) {
439
- // Pi now waits on tools that A2's tool handlers run.
439
+ // Pi now waits on tools that A2's tool consumer run.
440
440
  endStep(state)
441
441
  return pending(signal)
442
442
  }
@@ -962,7 +962,7 @@ function builtinDefinition(
962
962
 
963
963
  /**
964
964
  * The tools Pi's session declares: Pi's own, and the agent's. Their results
965
- * come from A2's log; A2's tool handlers ran them.
965
+ * come from A2's log; A2's tool consumer ran them.
966
966
  */
967
967
  async function sessionTools(
968
968
  modules: PiModules,
package/src/ai-server.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * experimental-a2/ai/server — `agent()`: the experimental-a2/ai protocol,
3
- * its model and tool handlers, and A2's store, scheduler, and telemetry,
3
+ * its model and tool consumers, and A2's store, scheduler, and telemetry,
4
4
  * assembled as one ordinary A2 server.
5
5
  */
6
6
 
7
- export { agent, runWithHandlerContext } from './ai-agent.ts'
7
+ export { agent, runWithConsumerContext } from './ai-agent.ts'
8
8
  export type {
9
9
  AISDKHarness,
10
10
  Agent,