experimental-a2 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +352 -0
- package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
- package/dist/actor-DBbe5xeE.d.ts.map +1 -0
- package/dist/actor-client.d.ts +2 -5
- package/dist/actor-client.d.ts.map +1 -1
- package/dist/actor-client.js +13 -4
- package/dist/actor-client.js.map +1 -1
- package/dist/actor-react.d.ts +5 -3
- package/dist/actor-react.d.ts.map +1 -1
- package/dist/actor-react.js +20 -10
- package/dist/actor-react.js.map +1 -1
- package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
- package/dist/actor-shared-t7w8xIKP.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +42 -23
- package/dist/actor.js.map +1 -1
- package/dist/{ai-DgOBltJ_.js → ai-UrWfKeuv.js} +621 -216
- package/dist/ai-UrWfKeuv.js.map +1 -0
- package/dist/ai-client.d.ts +27 -0
- package/dist/ai-client.d.ts.map +1 -0
- package/dist/ai-client.js +29 -0
- package/dist/ai-client.js.map +1 -0
- package/dist/ai-react.d.ts +96 -0
- package/dist/ai-react.d.ts.map +1 -0
- package/dist/ai-react.js +142 -0
- package/dist/ai-react.js.map +1 -0
- package/dist/ai-server.browser.js +2 -2
- package/dist/ai-server.browser.js.map +1 -1
- package/dist/ai-server.d.ts +80 -36
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +432 -258
- package/dist/ai-server.js.map +1 -1
- package/dist/{ai-CrEf6p_W.d.ts → ai-vpGtcaCB.d.ts} +114 -100
- package/dist/ai-vpGtcaCB.d.ts.map +1 -0
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
- package/dist/client-COaiaDub.js.map +1 -0
- package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
- package/dist/client-qRnRUzBS.d.ts.map +1 -0
- package/dist/client.d.ts +4 -2
- package/dist/client.js +1 -1
- package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
- package/dist/contract-BYKASlCV.js.map +1 -0
- package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
- package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
- package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
- package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
- package/dist/internal-CbVvEwad.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/react-BbJ-g6Uu.d.ts +86 -0
- package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
- package/dist/react.d.ts +2 -122
- package/dist/react.js +107 -100
- package/dist/react.js.map +1 -1
- package/dist/reducer-BAXc8JyF.js +87 -0
- package/dist/reducer-BAXc8JyF.js.map +1 -0
- package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
- package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
- package/dist/reducer-runtime-DQwz_J59.js +28 -0
- package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +33 -10
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -0
- package/dist/scheduler-vercel.d.ts +4 -9
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +25 -44
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
- package/dist/server-CGM8WzRG.d.ts.map +1 -0
- package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
- package/dist/server-Rj64fNvX.js.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
- package/dist/session-clear-DGI4jTQ5.js.map +1 -0
- package/dist/session-clear-Dshpuhff.d.ts +12 -0
- package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
- package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
- package/dist/store-Gpkq7WBo.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +182 -69
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
- package/dist/store-polling-4_y8lETm.js.map +1 -0
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +399 -108
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
- package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.d.ts.map +1 -1
- package/dist/store-redis-http.js +293 -11
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
- package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +11 -11
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +166 -32
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-Cx7AAPGq.js +23 -0
- package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
- package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
- package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
- package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
- package/dist/wire-DjfHOvdW.js.map +1 -0
- package/docs/01-quickstart.mdx +2 -2
- package/docs/actors/01-introduction.mdx +4 -0
- package/docs/actors/04-routes.mdx +14 -1
- package/docs/concepts/01-contracts.mdx +95 -0
- package/docs/concepts/04-state.mdx +3 -3
- package/docs/guides/01-timers.mdx +22 -14
- package/docs/guides/02-cancellation.mdx +1 -1
- package/docs/guides/03-react.mdx +268 -328
- package/docs/guides/04-local-first.mdx +32 -73
- package/docs/guides/05-production.mdx +40 -27
- package/docs/guides/06-ai-agents.mdx +671 -581
- package/docs/guides/09-presence.mdx +3 -7
- package/docs/guides/10-transports.mdx +5 -5
- package/docs/index.mdx +10 -18
- package/docs/reference/01-api.mdx +656 -194
- package/docs/reference/02-errors.mdx +2 -1
- package/examples/playground/DEPLOY.md +52 -6
- package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
- package/examples/playground/app/a0/[appId]/page.tsx +48 -0
- package/examples/playground/app/a0/a0.css +818 -0
- package/examples/playground/app/a0/app-list.tsx +211 -0
- package/examples/playground/app/a0/app-view.tsx +55 -0
- package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
- package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
- package/examples/playground/app/a0/apps.test.ts +116 -0
- package/examples/playground/app/a0/apps.ts +87 -0
- package/examples/playground/app/a0/chat.tsx +364 -0
- package/examples/playground/app/a0/columns.tsx +109 -0
- package/examples/playground/app/a0/context-menu.tsx +116 -0
- package/examples/playground/app/a0/cookies.ts +46 -0
- package/examples/playground/app/a0/crash-on-request.ts +72 -0
- package/examples/playground/app/a0/crashes/route.ts +3 -0
- package/examples/playground/app/a0/crashes.test.ts +33 -0
- package/examples/playground/app/a0/crashes.ts +28 -0
- package/examples/playground/app/a0/devtools.tsx +164 -0
- package/examples/playground/app/a0/events/route.ts +6 -0
- package/examples/playground/app/a0/flags.ts +30 -0
- package/examples/playground/app/a0/layout.tsx +38 -0
- package/examples/playground/app/a0/list/route.ts +10 -0
- package/examples/playground/app/a0/model.test.ts +56 -0
- package/examples/playground/app/a0/model.ts +58 -0
- package/examples/playground/app/a0/page.tsx +14 -0
- package/examples/playground/app/a0/preview-pane.tsx +145 -0
- package/examples/playground/app/a0/preview.test.ts +53 -0
- package/examples/playground/app/a0/preview.ts +26 -0
- package/examples/playground/app/a0/sandbox-log.ts +69 -0
- package/examples/playground/app/a0/sandbox.ts +191 -0
- package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
- package/examples/playground/app/a0/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/server.ts +158 -0
- package/examples/playground/app/a0/session.ts +47 -0
- package/examples/playground/app/a0/shell.tsx +150 -0
- package/examples/playground/app/a0/short/events/route.ts +9 -0
- package/examples/playground/app/a0/splitter.tsx +120 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
- package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
- package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
- package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
- package/examples/playground/app/agent/events/route.ts +5 -2
- package/examples/playground/app/agent/ingress.test.ts +98 -0
- package/examples/playground/app/agent/model-input.test.tsx +58 -0
- package/examples/playground/app/agent/model-input.tsx +115 -0
- package/examples/playground/app/agent/model.test.ts +73 -0
- package/examples/playground/app/agent/model.ts +58 -26
- package/examples/playground/app/agent/new-agent-session.tsx +37 -19
- package/examples/playground/app/agent/page.tsx +4 -4
- package/examples/playground/app/agent/server.ts +15 -14
- package/examples/playground/app/agent/session.ts +5 -6
- package/examples/playground/app/agent/telemetry.test.ts +168 -0
- package/examples/playground/app/agent/telemetry.ts +30 -0
- package/examples/playground/app/agent/trace.test.ts +23 -0
- package/examples/playground/app/agent/trace.ts +18 -0
- package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
- package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
- package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
- package/examples/playground/app/canvas/model.ts +53 -53
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
- package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
- package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
- package/examples/playground/app/chat/[chatId]/session.ts +2 -19
- package/examples/playground/app/chat/agent/route.ts +9 -0
- package/examples/playground/app/chat/model.test.ts +46 -119
- package/examples/playground/app/chat/model.ts +82 -128
- package/examples/playground/app/chat/scheduler/route.ts +2 -2
- package/examples/playground/app/chat/server.test.ts +108 -0
- package/examples/playground/app/chat/server.ts +69 -73
- package/examples/playground/app/components/activity-feed.tsx +30 -1
- package/examples/playground/app/components/activity-history.test.tsx +35 -0
- package/examples/playground/app/components/demo-icons.tsx +89 -0
- package/examples/playground/app/components/demos.tsx +33 -0
- package/examples/playground/app/components/fast-link.test.tsx +58 -0
- package/examples/playground/app/components/fast-link.tsx +55 -0
- package/examples/playground/app/components/site-header.tsx +96 -0
- package/examples/playground/app/counter/counter-client.tsx +3 -3
- package/examples/playground/app/counter/model.test.ts +3 -3
- package/examples/playground/app/counter/model.ts +11 -21
- package/examples/playground/app/counter/page.tsx +2 -15
- package/examples/playground/app/counter/session.ts +2 -2
- package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
- package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
- package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
- package/examples/playground/app/documents/[documentId]/session.ts +2 -2
- package/examples/playground/app/documents/model.ts +27 -27
- package/examples/playground/app/globals.css +420 -207
- package/examples/playground/app/initial-state.test.tsx +54 -0
- package/examples/playground/app/layout.tsx +7 -23
- package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
- package/examples/playground/app/orders/[orderId]/session.ts +2 -2
- package/examples/playground/app/orders/model.ts +49 -49
- package/examples/playground/app/page.tsx +13 -102
- package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
- package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
- package/examples/playground/app/recovery/model.ts +30 -30
- package/examples/playground/instrumentation.ts +5 -0
- package/examples/playground/package.json +10 -2
- package/examples/playground/scripts/a0-template.ts +77 -0
- package/examples/playground/vercel.json +18 -0
- package/package.json +3 -1
- package/src/actor-client.ts +16 -4
- package/src/actor-react.ts +25 -11
- package/src/actor-shared.ts +21 -6
- package/src/actor.ts +76 -31
- package/src/ai-agent-actions.ts +219 -0
- package/src/ai-agent.ts +2343 -0
- package/src/ai-app-state.ts +91 -0
- package/src/ai-client-state.ts +6 -3
- package/src/ai-client.ts +62 -0
- package/src/ai-context-schema.ts +25 -5
- package/src/ai-context.ts +90 -40
- package/src/ai-control-server.ts +92 -72
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +262 -64
- package/src/ai-progress-batches.ts +26 -0
- package/src/ai-projector.ts +95 -14
- package/src/ai-react.ts +146 -0
- package/src/ai-server.browser.ts +2 -2
- package/src/ai-server.ts +20 -1964
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +339 -155
- package/src/bootstrap-stream.ts +72 -0
- package/src/bounded-cache.ts +55 -0
- package/src/client-render-session.ts +49 -0
- package/src/client.ts +688 -151
- package/src/contract.ts +92 -42
- package/src/deterministic-id.ts +37 -0
- package/src/equal-json.ts +32 -0
- package/src/errors.ts +13 -1
- package/src/index.ts +5 -1
- package/src/initial-snapshot.ts +39 -0
- package/src/internal.ts +45 -0
- package/src/postgres-notification-scope.ts +14 -3
- package/src/react.ts +171 -206
- package/src/redis-http-reply.ts +256 -0
- package/src/redis-http-stream.ts +73 -0
- package/src/redis-http-subscriptions.ts +13 -5
- package/src/reducer-runtime.ts +52 -0
- package/src/reducer.ts +92 -10
- package/src/scheduler-qstash.ts +49 -20
- package/src/scheduler-task.ts +34 -2
- package/src/scheduler-vercel.ts +32 -84
- package/src/server-fetch.ts +139 -35
- package/src/server.ts +957 -237
- package/src/session-clear.ts +23 -0
- package/src/session-socket.ts +154 -49
- package/src/sse.ts +71 -4
- package/src/store-memory.ts +220 -78
- package/src/store-polling.ts +18 -4
- package/src/store-postgres.ts +461 -114
- package/src/store-redis-core.ts +1260 -434
- package/src/store-redis-http.ts +18 -2
- package/src/store-redis-notify.ts +260 -85
- package/src/store-redis.ts +19 -9
- package/src/store-sqlite.ts +259 -45
- package/src/store.ts +46 -1
- package/src/stream-buffer.ts +43 -0
- package/src/telemetry.ts +13 -1
- package/src/wire.ts +241 -13
- package/dist/actor-ohPC-81x.d.ts.map +0 -1
- package/dist/actor-shared-B5tJfzt-.js.map +0 -1
- package/dist/ai-CrEf6p_W.d.ts.map +0 -1
- package/dist/ai-DgOBltJ_.js.map +0 -1
- package/dist/client-BYzHjkwU.js.map +0 -1
- package/dist/client-CzyacQpJ.d.ts.map +0 -1
- package/dist/contract-CKRg_E4q.js.map +0 -1
- package/dist/errors-DCk6ch5n.js.map +0 -1
- package/dist/errors-DvhSXnxk.d.ts.map +0 -1
- package/dist/internal-Dq2qYxou.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/reducer-DEMjEY_O.js +0 -29
- package/dist/reducer-DEMjEY_O.js.map +0 -1
- package/dist/reducer-otzHjuJj.d.ts.map +0 -1
- package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
- package/dist/server-BD5ckxZb.js.map +0 -1
- package/dist/server-DbD7IJVK.d.ts.map +0 -1
- package/dist/store-DyZM6fS5.d.ts.map +0 -1
- package/dist/store-polling-CmxUbV93.js.map +0 -1
- package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
- package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
- package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
- package/dist/validate-XKT4FSNn.js +0 -30
- package/dist/validate-XKT4FSNn.js.map +0 -1
- package/dist/wire-BO5wWCb1.js.map +0 -1
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
- package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
- package/examples/playground/app/agent/compaction-settings.ts +0 -49
- package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
- package/examples/playground/app/agent/compaction-timeline.ts +0 -208
package/src/ai-server.ts
CHANGED
|
@@ -1,1970 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* experimental-a2/ai/server — the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `createAgentServer()` is its batteries-included assembly with createServer.
|
|
2
|
+
* experimental-a2/ai/server — `agent()`: the experimental-a2/ai protocol,
|
|
3
|
+
* its model and tool handlers, and A2's store, scheduler, and telemetry,
|
|
4
|
+
* assembled as one ordinary A2 server.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import type { ControlCommit, ControlCommand } from './ai-control.ts'
|
|
23
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
24
|
-
import { createHash } from 'node:crypto'
|
|
25
|
-
import { convertToModelMessages, isToolUIPart } from 'ai'
|
|
26
|
-
import type {
|
|
27
|
-
FinishReason,
|
|
7
|
+
export { agent, runWithHandlerContext } from './ai-agent.ts'
|
|
8
|
+
export type {
|
|
9
|
+
Agent,
|
|
10
|
+
AgentFromContractOptions,
|
|
11
|
+
AgentGenerate,
|
|
12
|
+
AgentGenerateContext,
|
|
13
|
+
AgentGenerationSettings,
|
|
14
|
+
AgentMessageMetadata,
|
|
15
|
+
AgentModelOptions,
|
|
16
|
+
AgentOptions,
|
|
17
|
+
AgentResolverContext,
|
|
18
|
+
AutomaticCompaction,
|
|
19
|
+
CompactionOptions,
|
|
20
|
+
CompactionPolicy,
|
|
28
21
|
Instructions,
|
|
29
22
|
LanguageModel,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
TextStreamPart,
|
|
23
|
+
ModelSettings,
|
|
24
|
+
Resolvable,
|
|
33
25
|
ToolSet,
|
|
34
|
-
|
|
35
|
-
UIMessageChunk,
|
|
36
|
-
} from 'ai'
|
|
37
|
-
import { generateAISDKStep, type AISDKStepSettings } from './ai-sdk-step.ts'
|
|
38
|
-
import { gatewayModelId, readModelLimits } from './ai-model-metadata.ts'
|
|
39
|
-
import { estimateInputTokens, measuredInputTokens } from './ai-input-tokens.ts'
|
|
40
|
-
import type {
|
|
41
|
-
AIEventDefs,
|
|
42
|
-
AgentDefinition,
|
|
43
|
-
AgentToolContext,
|
|
44
|
-
AgentToolEventDefs,
|
|
45
|
-
ApprovalRequestedPayload,
|
|
46
|
-
CompactionCompletedPayload,
|
|
47
|
-
CompactionRequestedPayload,
|
|
48
|
-
GenerationCompletedPayload,
|
|
49
|
-
GenerationFailedPayload,
|
|
50
|
-
GenerationProgressPayload,
|
|
51
|
-
GenerationStartedPayload,
|
|
52
|
-
MessageCompletedPayload,
|
|
53
|
-
ToolCalledPayload,
|
|
54
|
-
ToolResultPayload,
|
|
55
|
-
} from './ai.ts'
|
|
56
|
-
import type { AICoordinatorState } from './ai-coordinator.ts'
|
|
57
|
-
import type { AppendInput, ContractEvent, EventDefs } from './contract.ts'
|
|
58
|
-
import { A2Error } from './errors.ts'
|
|
59
|
-
import {
|
|
60
|
-
consumeSchedulerSendFailure,
|
|
61
|
-
installAmbientToolScopeStorage,
|
|
62
|
-
type AmbientToolScope,
|
|
63
|
-
} from './internal.ts'
|
|
64
|
-
import {
|
|
65
|
-
createServer,
|
|
66
|
-
type A2Server,
|
|
67
|
-
type HandlerContext,
|
|
68
|
-
type ServerOptions,
|
|
69
|
-
} from './server.ts'
|
|
70
|
-
import {
|
|
71
|
-
setServerFetchHooks,
|
|
72
|
-
type ServerIngressContext,
|
|
73
|
-
} from './server-fetch.ts'
|
|
74
|
-
|
|
75
|
-
function validateAgentIngress(context: ServerIngressContext): void {
|
|
76
|
-
const rejected = context.events.find((event) => {
|
|
77
|
-
if (event.type !== 'ai.control.requested') return true
|
|
78
|
-
const command = event.payload as Record<string, unknown>
|
|
79
|
-
if (
|
|
80
|
-
command['action'] === 'request-input' ||
|
|
81
|
-
command['action'] === 'tool-result'
|
|
82
|
-
)
|
|
83
|
-
return true
|
|
84
|
-
if (
|
|
85
|
-
command['action'] === 'send' ||
|
|
86
|
-
command['action'] === 'edit' ||
|
|
87
|
-
command['action'] === 'steer'
|
|
88
|
-
) {
|
|
89
|
-
return (
|
|
90
|
-
(command['message'] as { role?: unknown } | undefined)?.role !== 'user'
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
return false
|
|
94
|
-
})
|
|
95
|
-
if (rejected !== undefined) {
|
|
96
|
-
throw new A2Error(
|
|
97
|
-
'INVALID_PAYLOAD',
|
|
98
|
-
`event '${rejected.type}' is server-only for an AI agent`,
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
type AgentGenerationProgress = {
|
|
104
|
-
type: 'progress'
|
|
105
|
-
chunks: UIMessageChunk[]
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
type AgentGenerationFinish = {
|
|
109
|
-
type: 'finish'
|
|
110
|
-
finishReason?: FinishReason
|
|
111
|
-
usage?: LanguageModelUsage
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
type AgentGenerationUpdate = AgentGenerationProgress | AgentGenerationFinish
|
|
115
|
-
|
|
116
|
-
export type AgentGenerateContext<
|
|
117
|
-
M extends UIMessage,
|
|
118
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
119
|
-
T extends ToolSet,
|
|
120
|
-
> = {
|
|
121
|
-
request: ContractEvent<D, 'ai.generation.requested'>
|
|
122
|
-
requestId: string
|
|
123
|
-
generationId: string
|
|
124
|
-
responseMessageId: string
|
|
125
|
-
messages: M[]
|
|
126
|
-
modelMessages: ModelMessage[]
|
|
127
|
-
session: Pick<HandlerContext<D>['session'], 'state'>
|
|
128
|
-
signal: AbortSignal
|
|
129
|
-
model: LanguageModel
|
|
130
|
-
tools: T
|
|
131
|
-
instructions?: Instructions
|
|
132
|
-
generation: AgentGenerationSettings<T>
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export type AgentGenerate<
|
|
136
|
-
M extends UIMessage,
|
|
137
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
138
|
-
T extends ToolSet,
|
|
139
|
-
> = (
|
|
140
|
-
context: AgentGenerateContext<M, D, T>,
|
|
141
|
-
) => ReadableStream<UIMessageChunk> | Promise<ReadableStream<UIMessageChunk>>
|
|
142
|
-
|
|
143
|
-
type UIMessageMetadata<M extends UIMessage> =
|
|
144
|
-
M extends UIMessage<infer Metadata> ? Metadata : unknown
|
|
145
|
-
|
|
146
|
-
export type AgentMessageMetadata<
|
|
147
|
-
M extends UIMessage,
|
|
148
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
149
|
-
T extends ToolSet,
|
|
150
|
-
> = (
|
|
151
|
-
context: AgentGenerateContext<M, D, T> & { part: TextStreamPart<T> },
|
|
152
|
-
) => UIMessageMetadata<M> | undefined
|
|
153
|
-
|
|
154
|
-
export type AgentResolverContext<
|
|
155
|
-
M extends UIMessage,
|
|
156
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
157
|
-
> = {
|
|
158
|
-
event: ContractEvent<D, 'ai.generation.requested'>
|
|
159
|
-
messages: M[]
|
|
160
|
-
session: Pick<HandlerContext<D>['session'], 'state'>
|
|
161
|
-
signal: AbortSignal
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export type Resolvable<T, C> = T | ((context: C) => T | Promise<T>)
|
|
165
|
-
|
|
166
|
-
export type CompactionPolicy<
|
|
167
|
-
M extends UIMessage,
|
|
168
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
169
|
-
> = {
|
|
170
|
-
shouldCompact(
|
|
171
|
-
context: AgentResolverContext<M, D> & {
|
|
172
|
-
messages: M[]
|
|
173
|
-
modelMessages: ModelMessage[]
|
|
174
|
-
},
|
|
175
|
-
): boolean | Promise<boolean>
|
|
176
|
-
compact(
|
|
177
|
-
context: AgentResolverContext<M, D> & {
|
|
178
|
-
messages: M[]
|
|
179
|
-
modelMessages: ModelMessage[]
|
|
180
|
-
},
|
|
181
|
-
): M[] | Promise<M[]>
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export type AutomaticCompaction = {
|
|
185
|
-
thresholdTokens?: number
|
|
186
|
-
instructions?: string
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export type CompactionOptions<
|
|
190
|
-
M extends UIMessage,
|
|
191
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
192
|
-
> = false | AutomaticCompaction | CompactionPolicy<M, D>
|
|
193
|
-
|
|
194
|
-
export type AgentGenerationSettings<T extends ToolSet> = AISDKStepSettings<T>
|
|
195
|
-
|
|
196
|
-
export type CreateHandlersOptions<
|
|
197
|
-
M extends UIMessage,
|
|
198
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
199
|
-
T extends ToolSet = ToolSet,
|
|
200
|
-
> = {
|
|
201
|
-
agent: AgentDefinition<M, D>
|
|
202
|
-
/** AI SDK model string or provider model, optionally resolved per generation. */
|
|
203
|
-
model: Resolvable<LanguageModel, AgentResolverContext<M, D>>
|
|
204
|
-
tools?: T
|
|
205
|
-
instructions?: Resolvable<
|
|
206
|
-
Instructions | undefined,
|
|
207
|
-
AgentResolverContext<M, D>
|
|
208
|
-
>
|
|
209
|
-
/** Settings for each one-step AI SDK streamText call. */
|
|
210
|
-
generation?: AgentGenerationSettings<T>
|
|
211
|
-
/**
|
|
212
|
-
* Maximum model steps in one assistant response. Defaults to Infinity.
|
|
213
|
-
*/
|
|
214
|
-
maxSteps?: number
|
|
215
|
-
/** Map default AI SDK stream parts to typed assistant message metadata. */
|
|
216
|
-
messageMetadata?: AgentMessageMetadata<M, D, T>
|
|
217
|
-
/**
|
|
218
|
-
* Replace model generation with an AI SDK UI message chunk stream.
|
|
219
|
-
* The custom stream owns its metadata chunks.
|
|
220
|
-
*/
|
|
221
|
-
generate?: AgentGenerate<M, D, T>
|
|
222
|
-
compaction?: Resolvable<CompactionOptions<M, D>, AgentResolverContext<M, D>>
|
|
223
|
-
/** Progress is durably flushed at either limit, whichever is reached first. */
|
|
224
|
-
progress?: { maxChunks?: number; maxDelayMs?: number }
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export type CreateAgentServerOptions<
|
|
228
|
-
M extends UIMessage,
|
|
229
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
230
|
-
T extends ToolSet = ToolSet,
|
|
231
|
-
> = CreateHandlersOptions<M, D, T> &
|
|
232
|
-
Omit<ServerOptions<D>, 'contract' | 'handlers'> & {
|
|
233
|
-
handlers?: ServerOptions<D>['handlers']
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const checkAbort = (signal: AbortSignal): boolean => {
|
|
237
|
-
if (!signal.aborted) return false
|
|
238
|
-
const reason: unknown = signal.reason
|
|
239
|
-
if (
|
|
240
|
-
reason instanceof A2Error &&
|
|
241
|
-
(reason.code === 'CLAIM_EXPIRED' || reason.code === 'SUPERSEDED_ATTEMPT')
|
|
242
|
-
) {
|
|
243
|
-
throw reason
|
|
244
|
-
}
|
|
245
|
-
return true
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const resolve = async <T, C extends { signal: AbortSignal }>(
|
|
249
|
-
value: Resolvable<T, C>,
|
|
250
|
-
context: C,
|
|
251
|
-
): Promise<T> => {
|
|
252
|
-
let result: T
|
|
253
|
-
try {
|
|
254
|
-
result =
|
|
255
|
-
typeof value === 'function'
|
|
256
|
-
? await (value as (context: C) => T | Promise<T>)(context)
|
|
257
|
-
: value
|
|
258
|
-
} catch (error) {
|
|
259
|
-
checkAbort(context.signal)
|
|
260
|
-
throw error
|
|
261
|
-
}
|
|
262
|
-
checkAbort(context.signal)
|
|
263
|
-
return result
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const modelName = (model: LanguageModel): string => {
|
|
267
|
-
if (typeof model === 'string') return model
|
|
268
|
-
if ('modelId' in model && typeof model.modelId === 'string') {
|
|
269
|
-
return model.modelId
|
|
270
|
-
}
|
|
271
|
-
return 'custom'
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const errorMessage = (error: unknown): string =>
|
|
275
|
-
error instanceof Error ? error.message : String(error)
|
|
276
|
-
|
|
277
|
-
const toolScopeStorage = installAmbientToolScopeStorage(
|
|
278
|
-
() => new AsyncLocalStorage<AmbientToolScope>(),
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
/** Run `fn` with `ctx` as the ambient tool handler context, for direct tool tests. */
|
|
282
|
-
export function runWithHandlerContext<D extends AgentToolEventDefs, R>(
|
|
283
|
-
ctx: AgentToolContext<D>,
|
|
284
|
-
fn: () => R,
|
|
285
|
-
): R {
|
|
286
|
-
return toolScopeStorage.run({ context: ctx }, fn)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const promptCacheKey = (sessionId: string, generationId: string): string =>
|
|
290
|
-
`${sessionId}\u001f${generationId}`
|
|
291
|
-
|
|
292
|
-
const asyncIterator = (value: unknown): AsyncIterator<unknown> | undefined => {
|
|
293
|
-
if (typeof value !== 'object' || value === null) return undefined
|
|
294
|
-
const factory = Reflect.get(value, Symbol.asyncIterator)
|
|
295
|
-
if (typeof factory !== 'function') return undefined
|
|
296
|
-
return (factory as (this: object) => AsyncIterator<unknown>).call(value)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const isBoundaryChunk = (chunk: UIMessageChunk | undefined): boolean =>
|
|
300
|
-
chunk !== undefined &&
|
|
301
|
-
(chunk.type === 'text-end' ||
|
|
302
|
-
chunk.type === 'reasoning-end' ||
|
|
303
|
-
chunk.type === 'tool-input-available' ||
|
|
304
|
-
chunk.type === 'tool-input-error' ||
|
|
305
|
-
chunk.type === 'tool-output-available' ||
|
|
306
|
-
chunk.type === 'tool-output-error' ||
|
|
307
|
-
chunk.type === 'tool-output-denied' ||
|
|
308
|
-
chunk.type === 'tool-approval-request' ||
|
|
309
|
-
chunk.type === 'finish' ||
|
|
310
|
-
chunk.type === 'abort' ||
|
|
311
|
-
chunk.type === 'error')
|
|
312
|
-
|
|
313
|
-
type StreamRead<T> = Awaited<ReturnType<ReadableStreamDefaultReader<T>['read']>>
|
|
314
|
-
|
|
315
|
-
const nextOrTimer = async <T>(
|
|
316
|
-
next: Promise<StreamRead<T>>,
|
|
317
|
-
delayMs: number,
|
|
318
|
-
): Promise<{ type: 'next'; result: StreamRead<T> } | { type: 'timer' }> => {
|
|
319
|
-
let timer: ReturnType<typeof setTimeout> | undefined
|
|
320
|
-
try {
|
|
321
|
-
return await Promise.race([
|
|
322
|
-
next.then((result) => ({ type: 'next' as const, result })),
|
|
323
|
-
new Promise<{ type: 'timer' }>((finishTimer) => {
|
|
324
|
-
timer = setTimeout(() => finishTimer({ type: 'timer' }), delayMs)
|
|
325
|
-
;(timer as { unref?: () => void }).unref?.()
|
|
326
|
-
}),
|
|
327
|
-
])
|
|
328
|
-
} finally {
|
|
329
|
-
if (timer !== undefined) clearTimeout(timer)
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
type GenerationCompletion = {
|
|
334
|
-
finishReason?: FinishReason
|
|
335
|
-
usage?: LanguageModelUsage
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
type GenerationSource = {
|
|
339
|
-
stream: ReadableStream<UIMessageChunk>
|
|
340
|
-
completion?: () => GenerationCompletion
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async function generateWithAISDK<
|
|
344
|
-
M extends UIMessage,
|
|
345
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
346
|
-
T extends ToolSet,
|
|
347
|
-
>(options: {
|
|
348
|
-
agentName: string
|
|
349
|
-
context: AgentGenerateContext<M, D, T>
|
|
350
|
-
messageMetadata: AgentMessageMetadata<M, D, T> | undefined
|
|
351
|
-
}): Promise<GenerationSource> {
|
|
352
|
-
const { agentName, context, messageMetadata } = options
|
|
353
|
-
const headers = context.generation.headers ?? {}
|
|
354
|
-
const generation =
|
|
355
|
-
gatewayModelId(context.model) === undefined
|
|
356
|
-
? context.generation
|
|
357
|
-
: {
|
|
358
|
-
...context.generation,
|
|
359
|
-
headers: Object.keys(headers).some(
|
|
360
|
-
(key) => key.toLowerCase() === 'x-session-affinity',
|
|
361
|
-
)
|
|
362
|
-
? headers
|
|
363
|
-
: {
|
|
364
|
-
...headers,
|
|
365
|
-
'x-session-affinity': createHash('sha256')
|
|
366
|
-
.update(
|
|
367
|
-
JSON.stringify([agentName, context.request.sessionId]),
|
|
368
|
-
)
|
|
369
|
-
.digest('hex'),
|
|
370
|
-
},
|
|
371
|
-
providerOptions: {
|
|
372
|
-
...context.generation.providerOptions,
|
|
373
|
-
gateway: {
|
|
374
|
-
caching: 'auto',
|
|
375
|
-
...context.generation.providerOptions?.['gateway'],
|
|
376
|
-
},
|
|
377
|
-
},
|
|
378
|
-
}
|
|
379
|
-
const step = await generateAISDKStep<M, T>({
|
|
380
|
-
model: context.model,
|
|
381
|
-
tools: context.tools,
|
|
382
|
-
messages: context.messages,
|
|
383
|
-
modelMessages: context.modelMessages,
|
|
384
|
-
responseMessageId: context.responseMessageId,
|
|
385
|
-
abortSignal: context.signal,
|
|
386
|
-
...(context.instructions === undefined
|
|
387
|
-
? {}
|
|
388
|
-
: { instructions: context.instructions }),
|
|
389
|
-
settings: generation,
|
|
390
|
-
...(messageMetadata === undefined
|
|
391
|
-
? {}
|
|
392
|
-
: {
|
|
393
|
-
messageMetadata: ({ part }) =>
|
|
394
|
-
messageMetadata({ ...context, generation, part }),
|
|
395
|
-
}),
|
|
396
|
-
})
|
|
397
|
-
return {
|
|
398
|
-
stream: step.stream as ReadableStream<UIMessageChunk>,
|
|
399
|
-
completion: step.completion,
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async function* consumeGeneration(options: {
|
|
404
|
-
source: GenerationSource
|
|
405
|
-
progress?: { maxChunks?: number; maxDelayMs?: number }
|
|
406
|
-
}): AsyncGenerator<AgentGenerationUpdate> {
|
|
407
|
-
const pending: UIMessageChunk[] = []
|
|
408
|
-
let streamedFinishReason: FinishReason | undefined
|
|
409
|
-
let streamedError: string | undefined
|
|
410
|
-
const maxChunks = options.progress?.maxChunks ?? 16
|
|
411
|
-
const maxDelayMs = options.progress?.maxDelayMs ?? 30
|
|
412
|
-
let lastFlush = Date.now()
|
|
413
|
-
const reader = options.source.stream.getReader()
|
|
414
|
-
let pendingChunk = reader.read()
|
|
415
|
-
try {
|
|
416
|
-
for (;;) {
|
|
417
|
-
let result: StreamRead<UIMessageChunk>
|
|
418
|
-
if (pending.length > 0) {
|
|
419
|
-
const remaining = Math.max(0, maxDelayMs - (Date.now() - lastFlush))
|
|
420
|
-
const outcome = await nextOrTimer(pendingChunk, remaining)
|
|
421
|
-
if (outcome.type === 'timer') {
|
|
422
|
-
yield { type: 'progress', chunks: pending.splice(0) }
|
|
423
|
-
lastFlush = Date.now()
|
|
424
|
-
continue
|
|
425
|
-
}
|
|
426
|
-
result = outcome.result
|
|
427
|
-
} else {
|
|
428
|
-
result = await pendingChunk
|
|
429
|
-
}
|
|
430
|
-
if (result.done) break
|
|
431
|
-
const chunk = result.value
|
|
432
|
-
pending.push(chunk)
|
|
433
|
-
if (chunk.type === 'finish') streamedFinishReason = chunk.finishReason
|
|
434
|
-
if (chunk.type === 'error') streamedError = chunk.errorText
|
|
435
|
-
pendingChunk = reader.read()
|
|
436
|
-
|
|
437
|
-
if (pending.length >= maxChunks || isBoundaryChunk(pending.at(-1))) {
|
|
438
|
-
yield { type: 'progress', chunks: pending.splice(0) }
|
|
439
|
-
lastFlush = Date.now()
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
} catch (error) {
|
|
443
|
-
if (pending.length > 0)
|
|
444
|
-
yield { type: 'progress', chunks: pending.splice(0) }
|
|
445
|
-
throw error
|
|
446
|
-
} finally {
|
|
447
|
-
void pendingChunk.catch(() => {})
|
|
448
|
-
try {
|
|
449
|
-
await reader.cancel()
|
|
450
|
-
} catch {
|
|
451
|
-
// Cleanup must not replace the error that ended generation.
|
|
452
|
-
} finally {
|
|
453
|
-
reader.releaseLock()
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
let completion: GenerationCompletion | undefined
|
|
458
|
-
try {
|
|
459
|
-
completion = options.source.completion?.()
|
|
460
|
-
} catch (error) {
|
|
461
|
-
if (pending.length > 0) {
|
|
462
|
-
yield { type: 'progress', chunks: pending.splice(0) }
|
|
463
|
-
}
|
|
464
|
-
throw error
|
|
465
|
-
}
|
|
466
|
-
if (pending.length > 0) {
|
|
467
|
-
yield { type: 'progress', chunks: pending.splice(0) }
|
|
468
|
-
}
|
|
469
|
-
if (streamedError !== undefined) throw new Error(streamedError)
|
|
470
|
-
const finishReason = completion?.finishReason ?? streamedFinishReason
|
|
471
|
-
yield {
|
|
472
|
-
type: 'finish',
|
|
473
|
-
...(finishReason === undefined ? {} : { finishReason }),
|
|
474
|
-
...(completion?.usage === undefined ? {} : { usage: completion.usage }),
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const foldAIEvents = <
|
|
479
|
-
M extends UIMessage,
|
|
480
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
481
|
-
>(options: {
|
|
482
|
-
reducer: Reducer<D, AIContextState<M>>
|
|
483
|
-
state: AIContextState<M>
|
|
484
|
-
events: ContractEvent<D>[]
|
|
485
|
-
}): AIContextState<M> => {
|
|
486
|
-
let state = options.state
|
|
487
|
-
for (const event of options.events) state = options.reducer.fold(state, event)
|
|
488
|
-
return state
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const summarize = async <
|
|
492
|
-
M extends UIMessage,
|
|
493
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
494
|
-
T extends ToolSet,
|
|
495
|
-
>(options: {
|
|
496
|
-
agentName: string
|
|
497
|
-
context: AgentGenerateContext<M, D, T>
|
|
498
|
-
instructions: string | undefined
|
|
499
|
-
}): Promise<{ summary: string; usage?: LanguageModelUsage }> => {
|
|
500
|
-
const instruction = [
|
|
501
|
-
'Summarize this conversation so you can continue the task after the earlier context is replaced by your summary.',
|
|
502
|
-
'Preserve the objective, constraints, decisions, exact identifiers, completed actions and their results, unresolved questions, and next steps. Distinguish observations from assumptions.',
|
|
503
|
-
'Respond only with the summary as text. Do not call any tools or continue working on the task.',
|
|
504
|
-
options.instructions,
|
|
505
|
-
]
|
|
506
|
-
.filter(Boolean)
|
|
507
|
-
.join('\n')
|
|
508
|
-
const generation = { ...options.context.generation }
|
|
509
|
-
for (const key of Object.keys(generation)) {
|
|
510
|
-
if (
|
|
511
|
-
key.startsWith('on') ||
|
|
512
|
-
key.startsWith('experimental_on') ||
|
|
513
|
-
[
|
|
514
|
-
'repairToolCall',
|
|
515
|
-
'experimental_repairToolCall',
|
|
516
|
-
'experimental_refineToolInput',
|
|
517
|
-
'experimental_transform',
|
|
518
|
-
'toolApproval',
|
|
519
|
-
].includes(key)
|
|
520
|
-
) {
|
|
521
|
-
Reflect.deleteProperty(generation, key)
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
const tools = Object.fromEntries(
|
|
525
|
-
Object.entries(options.context.tools).map(([name, tool]) => {
|
|
526
|
-
const definition = { ...tool, needsApproval: false }
|
|
527
|
-
for (const key of [
|
|
528
|
-
'execute',
|
|
529
|
-
'onInputStart',
|
|
530
|
-
'onInputDelta',
|
|
531
|
-
'onInputAvailable',
|
|
532
|
-
]) {
|
|
533
|
-
Reflect.deleteProperty(definition, key)
|
|
534
|
-
}
|
|
535
|
-
return [name, definition]
|
|
536
|
-
}),
|
|
537
|
-
) as T
|
|
538
|
-
const source = await generateWithAISDK({
|
|
539
|
-
agentName: options.agentName,
|
|
540
|
-
context: {
|
|
541
|
-
...options.context,
|
|
542
|
-
generation,
|
|
543
|
-
tools,
|
|
544
|
-
modelMessages: [
|
|
545
|
-
...options.context.modelMessages,
|
|
546
|
-
{ role: 'user', content: instruction },
|
|
547
|
-
],
|
|
548
|
-
responseMessageId: await aiEventId(
|
|
549
|
-
'a2.ai.summary',
|
|
550
|
-
options.context.generationId,
|
|
551
|
-
),
|
|
552
|
-
},
|
|
553
|
-
messageMetadata: undefined,
|
|
554
|
-
})
|
|
555
|
-
let summary = ''
|
|
556
|
-
let calledTool = false
|
|
557
|
-
let finish: AgentGenerationFinish | undefined
|
|
558
|
-
for await (const update of consumeGeneration({ source })) {
|
|
559
|
-
options.context.signal.throwIfAborted()
|
|
560
|
-
if (update.type === 'finish') {
|
|
561
|
-
finish = update
|
|
562
|
-
continue
|
|
563
|
-
}
|
|
564
|
-
for (const chunk of update.chunks) {
|
|
565
|
-
if (chunk.type === 'text-delta') summary += chunk.delta
|
|
566
|
-
if (chunk.type.startsWith('tool-')) calledTool = true
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
if (
|
|
570
|
-
calledTool ||
|
|
571
|
-
summary.trim().length === 0 ||
|
|
572
|
-
finish?.finishReason !== 'stop'
|
|
573
|
-
) {
|
|
574
|
-
throw new Error(
|
|
575
|
-
'compaction must produce a complete text summary without calling tools',
|
|
576
|
-
)
|
|
577
|
-
}
|
|
578
|
-
return {
|
|
579
|
-
summary,
|
|
580
|
-
...(finish.usage === undefined ? {} : { usage: finish.usage }),
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const activeCompaction = <M extends UIMessage>(
|
|
585
|
-
state: AIModelContext<M>,
|
|
586
|
-
): AIModelContext<M>['compaction'] => {
|
|
587
|
-
const compaction = state.compaction
|
|
588
|
-
return compaction?.status === 'completed' ? compaction : null
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
const contextMessages = <M extends UIMessage>({
|
|
592
|
-
state,
|
|
593
|
-
coordinator,
|
|
594
|
-
}: {
|
|
595
|
-
state: AIModelContext<M>
|
|
596
|
-
coordinator: AICoordinatorState
|
|
597
|
-
}): M[] => {
|
|
598
|
-
const queued = new Set(coordinator.queued.map((item) => item.messageId))
|
|
599
|
-
return state.messages.filter((message) => !queued.has(message.id))
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const modelContext = async <M extends UIMessage>(options: {
|
|
603
|
-
messages: M[]
|
|
604
|
-
state: AIModelContext<M>
|
|
605
|
-
tools: ToolSet
|
|
606
|
-
}): Promise<ModelMessage[]> => {
|
|
607
|
-
const messages = await convertToModelMessages(options.messages, {
|
|
608
|
-
tools: options.tools,
|
|
609
|
-
})
|
|
610
|
-
const summary = activeCompaction(options.state)?.summary
|
|
611
|
-
return summary === undefined
|
|
612
|
-
? messages
|
|
613
|
-
: [{ role: 'user', content: summary }, ...messages]
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
type ToolCallClassification = 'automatic' | 'approval' | 'unknown'
|
|
617
|
-
|
|
618
|
-
type PendingToolCall = {
|
|
619
|
-
call: ToolCalledPayload
|
|
620
|
-
classification: ToolCallClassification
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
const toolCalledEvent = async <D extends EventDefs>(
|
|
624
|
-
payload: ToolCalledPayload,
|
|
625
|
-
): Promise<AppendInput<D>> =>
|
|
626
|
-
({
|
|
627
|
-
type: 'ai.tool.called',
|
|
628
|
-
id: await aiEventId(
|
|
629
|
-
'a2.ai.tool.called',
|
|
630
|
-
payload.generationId,
|
|
631
|
-
payload.toolCallId,
|
|
632
|
-
),
|
|
633
|
-
payload,
|
|
634
|
-
}) as AppendInput<D>
|
|
635
|
-
|
|
636
|
-
const approvalClassification = <T extends ToolSet>(
|
|
637
|
-
tools: T,
|
|
638
|
-
generation: AgentGenerationSettings<T>,
|
|
639
|
-
toolName: string,
|
|
640
|
-
): ToolCallClassification => {
|
|
641
|
-
const configured = generation.toolApproval
|
|
642
|
-
if (typeof configured === 'function') return 'unknown'
|
|
643
|
-
const policy = configured?.[toolName]
|
|
644
|
-
if (typeof policy === 'function') return 'unknown'
|
|
645
|
-
if (
|
|
646
|
-
policy === 'user-approval' ||
|
|
647
|
-
(typeof policy === 'object' && policy?.type === 'user-approval')
|
|
648
|
-
) {
|
|
649
|
-
return 'approval'
|
|
650
|
-
}
|
|
651
|
-
if (
|
|
652
|
-
policy === 'denied' ||
|
|
653
|
-
(typeof policy === 'object' && policy?.type === 'denied')
|
|
654
|
-
) {
|
|
655
|
-
return 'approval'
|
|
656
|
-
}
|
|
657
|
-
const tool = tools[toolName] as { needsApproval?: unknown } | undefined
|
|
658
|
-
if (typeof tool?.needsApproval === 'function') return 'unknown'
|
|
659
|
-
return tool?.needsApproval === true ? 'approval' : 'automatic'
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
const lifecycleEvents = async <
|
|
663
|
-
D extends EventDefs,
|
|
664
|
-
T extends ToolSet,
|
|
665
|
-
>(options: {
|
|
666
|
-
requestId: string
|
|
667
|
-
messageId: string
|
|
668
|
-
generationId: string
|
|
669
|
-
sequence: number
|
|
670
|
-
chunks: UIMessageChunk[]
|
|
671
|
-
pending: PendingToolCall[]
|
|
672
|
-
tools: T
|
|
673
|
-
generation: AgentGenerationSettings<T>
|
|
674
|
-
custom: boolean
|
|
675
|
-
}): Promise<{ events: AppendInput<D>[]; pending: PendingToolCall[] }> => {
|
|
676
|
-
const result: AppendInput<D>[] = []
|
|
677
|
-
const pending = [...options.pending]
|
|
678
|
-
const take = (toolCallId: string): PendingToolCall | undefined => {
|
|
679
|
-
const index = pending.findIndex(
|
|
680
|
-
(candidate) => candidate.call.toolCallId === toolCallId,
|
|
681
|
-
)
|
|
682
|
-
if (index === -1) return undefined
|
|
683
|
-
return pending.splice(index, 1)[0]
|
|
684
|
-
}
|
|
685
|
-
const flush = async (toolCallId: string): Promise<void> => {
|
|
686
|
-
const pendingCall = take(toolCallId)
|
|
687
|
-
if (pendingCall) result.push(await toolCalledEvent<D>(pendingCall.call))
|
|
688
|
-
}
|
|
689
|
-
for (const [chunkIndex, chunk] of options.chunks.entries()) {
|
|
690
|
-
const resultId = (): Promise<string> =>
|
|
691
|
-
aiEventId(
|
|
692
|
-
'a2.ai.tool.stream-result',
|
|
693
|
-
options.generationId,
|
|
694
|
-
String(options.sequence),
|
|
695
|
-
String(chunkIndex),
|
|
696
|
-
)
|
|
697
|
-
if (chunk.type === 'tool-input-available') {
|
|
698
|
-
const payload: ToolCalledPayload = {
|
|
699
|
-
requestId: options.requestId,
|
|
700
|
-
messageId: options.messageId,
|
|
701
|
-
generationId: options.generationId,
|
|
702
|
-
toolCallId: chunk.toolCallId,
|
|
703
|
-
toolName: chunk.toolName,
|
|
704
|
-
input: chunk.input,
|
|
705
|
-
...(chunk.dynamic === undefined ? {} : { dynamic: chunk.dynamic }),
|
|
706
|
-
...(chunk.providerExecuted === undefined
|
|
707
|
-
? {}
|
|
708
|
-
: { providerExecuted: chunk.providerExecuted }),
|
|
709
|
-
...((
|
|
710
|
-
options.tools[chunk.toolName] as
|
|
711
|
-
{ supportsDeferredResults?: boolean } | undefined
|
|
712
|
-
)?.supportsDeferredResults === true
|
|
713
|
-
? { supportsDeferredResults: true }
|
|
714
|
-
: {}),
|
|
715
|
-
...(chunk.providerMetadata === undefined
|
|
716
|
-
? {}
|
|
717
|
-
: { providerMetadata: chunk.providerMetadata }),
|
|
718
|
-
...(chunk.toolMetadata === undefined
|
|
719
|
-
? {}
|
|
720
|
-
: { toolMetadata: chunk.toolMetadata }),
|
|
721
|
-
...(chunk.title === undefined ? {} : { title: chunk.title }),
|
|
722
|
-
}
|
|
723
|
-
take(payload.toolCallId)
|
|
724
|
-
const classification = approvalClassification(
|
|
725
|
-
options.tools,
|
|
726
|
-
options.generation,
|
|
727
|
-
chunk.toolName,
|
|
728
|
-
)
|
|
729
|
-
if (classification === 'automatic') {
|
|
730
|
-
result.push(await toolCalledEvent<D>(payload))
|
|
731
|
-
} else {
|
|
732
|
-
pending.push({ call: payload, classification })
|
|
733
|
-
}
|
|
734
|
-
} else if (chunk.type === 'tool-input-error') {
|
|
735
|
-
const payload: ToolResultPayload = {
|
|
736
|
-
requestId: options.requestId,
|
|
737
|
-
messageId: options.messageId,
|
|
738
|
-
generationId: options.generationId,
|
|
739
|
-
toolCallId: chunk.toolCallId,
|
|
740
|
-
toolName: chunk.toolName,
|
|
741
|
-
...(chunk.dynamic ? { input: chunk.input } : { rawInput: chunk.input }),
|
|
742
|
-
error: chunk.errorText,
|
|
743
|
-
phase: 'input',
|
|
744
|
-
...(chunk.dynamic === undefined ? {} : { dynamic: chunk.dynamic }),
|
|
745
|
-
...(chunk.providerExecuted === undefined
|
|
746
|
-
? {}
|
|
747
|
-
: { providerExecuted: chunk.providerExecuted }),
|
|
748
|
-
...(chunk.providerMetadata === undefined
|
|
749
|
-
? {}
|
|
750
|
-
: { providerMetadata: chunk.providerMetadata }),
|
|
751
|
-
...(chunk.toolMetadata === undefined
|
|
752
|
-
? {}
|
|
753
|
-
: { toolMetadata: chunk.toolMetadata }),
|
|
754
|
-
}
|
|
755
|
-
const called: ToolCalledPayload = {
|
|
756
|
-
requestId: options.requestId,
|
|
757
|
-
messageId: options.messageId,
|
|
758
|
-
generationId: options.generationId,
|
|
759
|
-
toolCallId: chunk.toolCallId,
|
|
760
|
-
toolName: chunk.toolName,
|
|
761
|
-
input: chunk.input,
|
|
762
|
-
}
|
|
763
|
-
take(chunk.toolCallId)
|
|
764
|
-
result.push(await toolCalledEvent<D>(called), {
|
|
765
|
-
type: 'ai.tool.result',
|
|
766
|
-
id: await resultId(),
|
|
767
|
-
payload,
|
|
768
|
-
} as AppendInput<D>)
|
|
769
|
-
} else if (chunk.type === 'tool-output-available') {
|
|
770
|
-
await flush(chunk.toolCallId)
|
|
771
|
-
const payload: ToolResultPayload = {
|
|
772
|
-
requestId: options.requestId,
|
|
773
|
-
messageId: options.messageId,
|
|
774
|
-
generationId: options.generationId,
|
|
775
|
-
toolCallId: chunk.toolCallId,
|
|
776
|
-
output: chunk.output,
|
|
777
|
-
phase: 'execution',
|
|
778
|
-
...(chunk.preliminary === undefined
|
|
779
|
-
? {}
|
|
780
|
-
: { preliminary: chunk.preliminary }),
|
|
781
|
-
...(chunk.dynamic === undefined ? {} : { dynamic: chunk.dynamic }),
|
|
782
|
-
...(chunk.providerExecuted === undefined
|
|
783
|
-
? {}
|
|
784
|
-
: { providerExecuted: chunk.providerExecuted }),
|
|
785
|
-
...(chunk.providerMetadata === undefined
|
|
786
|
-
? {}
|
|
787
|
-
: { providerMetadata: chunk.providerMetadata }),
|
|
788
|
-
...(chunk.toolMetadata === undefined
|
|
789
|
-
? {}
|
|
790
|
-
: { toolMetadata: chunk.toolMetadata }),
|
|
791
|
-
}
|
|
792
|
-
result.push({
|
|
793
|
-
type: 'ai.tool.result',
|
|
794
|
-
id: await resultId(),
|
|
795
|
-
payload,
|
|
796
|
-
} as AppendInput<D>)
|
|
797
|
-
} else if (chunk.type === 'tool-output-error') {
|
|
798
|
-
await flush(chunk.toolCallId)
|
|
799
|
-
const payload: ToolResultPayload = {
|
|
800
|
-
requestId: options.requestId,
|
|
801
|
-
messageId: options.messageId,
|
|
802
|
-
generationId: options.generationId,
|
|
803
|
-
toolCallId: chunk.toolCallId,
|
|
804
|
-
error: chunk.errorText,
|
|
805
|
-
phase: 'execution',
|
|
806
|
-
...(chunk.dynamic === undefined ? {} : { dynamic: chunk.dynamic }),
|
|
807
|
-
...(chunk.providerExecuted === undefined
|
|
808
|
-
? {}
|
|
809
|
-
: { providerExecuted: chunk.providerExecuted }),
|
|
810
|
-
...(chunk.providerMetadata === undefined
|
|
811
|
-
? {}
|
|
812
|
-
: { providerMetadata: chunk.providerMetadata }),
|
|
813
|
-
...(chunk.toolMetadata === undefined
|
|
814
|
-
? {}
|
|
815
|
-
: { toolMetadata: chunk.toolMetadata }),
|
|
816
|
-
}
|
|
817
|
-
result.push({
|
|
818
|
-
type: 'ai.tool.result',
|
|
819
|
-
id: await resultId(),
|
|
820
|
-
payload,
|
|
821
|
-
} as AppendInput<D>)
|
|
822
|
-
} else if (chunk.type === 'tool-output-denied') {
|
|
823
|
-
await flush(chunk.toolCallId)
|
|
824
|
-
const payload: ToolResultPayload = {
|
|
825
|
-
requestId: options.requestId,
|
|
826
|
-
messageId: options.messageId,
|
|
827
|
-
generationId: options.generationId,
|
|
828
|
-
toolCallId: chunk.toolCallId,
|
|
829
|
-
denied: true,
|
|
830
|
-
phase: 'execution',
|
|
831
|
-
}
|
|
832
|
-
result.push({
|
|
833
|
-
type: 'ai.tool.result',
|
|
834
|
-
id: await resultId(),
|
|
835
|
-
payload,
|
|
836
|
-
} as AppendInput<D>)
|
|
837
|
-
} else if (chunk.type === 'tool-approval-request') {
|
|
838
|
-
await flush(chunk.toolCallId)
|
|
839
|
-
const payload: ApprovalRequestedPayload = {
|
|
840
|
-
requestId: options.requestId,
|
|
841
|
-
messageId: options.messageId,
|
|
842
|
-
generationId: options.generationId,
|
|
843
|
-
approvalId: chunk.approvalId,
|
|
844
|
-
toolCallId: chunk.toolCallId,
|
|
845
|
-
...(chunk.isAutomatic === undefined
|
|
846
|
-
? {}
|
|
847
|
-
: { isAutomatic: chunk.isAutomatic }),
|
|
848
|
-
...(chunk.signature === undefined
|
|
849
|
-
? {}
|
|
850
|
-
: { signature: chunk.signature }),
|
|
851
|
-
}
|
|
852
|
-
result.push({
|
|
853
|
-
type: 'ai.approval.requested',
|
|
854
|
-
id: await aiEventId(
|
|
855
|
-
'a2.ai.approval.requested',
|
|
856
|
-
options.generationId,
|
|
857
|
-
chunk.approvalId,
|
|
858
|
-
),
|
|
859
|
-
payload,
|
|
860
|
-
} as AppendInput<D>)
|
|
861
|
-
} else if (chunk.type === 'tool-approval-response') {
|
|
862
|
-
result.push({
|
|
863
|
-
type: 'ai.approval.responded',
|
|
864
|
-
id: await aiEventId(
|
|
865
|
-
'a2.ai.approval.automatic-response',
|
|
866
|
-
options.generationId,
|
|
867
|
-
chunk.approvalId,
|
|
868
|
-
),
|
|
869
|
-
payload: {
|
|
870
|
-
requestId: options.requestId,
|
|
871
|
-
messageId: options.messageId,
|
|
872
|
-
generationId: options.generationId,
|
|
873
|
-
approvalId: chunk.approvalId,
|
|
874
|
-
approved: chunk.approved,
|
|
875
|
-
...(chunk.reason === undefined ? {} : { reason: chunk.reason }),
|
|
876
|
-
},
|
|
877
|
-
} as AppendInput<D>)
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
return { events: result, pending }
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
const validateCompaction = <
|
|
884
|
-
M extends UIMessage,
|
|
885
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
886
|
-
T extends ToolSet,
|
|
887
|
-
>(options: {
|
|
888
|
-
compaction: CompactionOptions<M, D>
|
|
889
|
-
explicit: boolean
|
|
890
|
-
generation: AgentGenerationSettings<T> | undefined
|
|
891
|
-
tools: T | undefined
|
|
892
|
-
}): CompactionOptions<M, D> => {
|
|
893
|
-
let compaction = options.compaction
|
|
894
|
-
if (
|
|
895
|
-
compaction !== false &&
|
|
896
|
-
(typeof compaction !== 'object' || compaction === null)
|
|
897
|
-
) {
|
|
898
|
-
throw new TypeError('compaction must resolve to false or a policy object')
|
|
899
|
-
}
|
|
900
|
-
if (compaction !== false && 'then' in compaction) {
|
|
901
|
-
void Promise.resolve(compaction).catch(() => {})
|
|
902
|
-
throw new TypeError('compaction options must be a policy or a resolver')
|
|
903
|
-
}
|
|
904
|
-
if (compaction !== false && !('shouldCompact' in compaction)) {
|
|
905
|
-
if (
|
|
906
|
-
compaction.thresholdTokens !== undefined &&
|
|
907
|
-
(!Number.isSafeInteger(compaction.thresholdTokens) ||
|
|
908
|
-
compaction.thresholdTokens < 1)
|
|
909
|
-
) {
|
|
910
|
-
throw new TypeError(
|
|
911
|
-
'compaction.thresholdTokens must be a positive safe integer',
|
|
912
|
-
)
|
|
913
|
-
}
|
|
914
|
-
const choice = options.generation?.toolChoice
|
|
915
|
-
const unsupported =
|
|
916
|
-
options.generation?.output !== undefined
|
|
917
|
-
? 'structured output'
|
|
918
|
-
: choice !== undefined && choice !== 'auto' && choice !== 'none'
|
|
919
|
-
? 'forced tool choice'
|
|
920
|
-
: Object.values(options.tools ?? {}).some(
|
|
921
|
-
(tool) =>
|
|
922
|
-
tool.type === 'provider' && tool.isProviderExecuted === true,
|
|
923
|
-
)
|
|
924
|
-
? 'provider-executed tools'
|
|
925
|
-
: undefined
|
|
926
|
-
if (unsupported !== undefined) {
|
|
927
|
-
if (options.explicit) {
|
|
928
|
-
throw new TypeError(
|
|
929
|
-
`automatic compaction does not support ${unsupported}; use a custom policy or compaction: false`,
|
|
930
|
-
)
|
|
931
|
-
}
|
|
932
|
-
compaction = false
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
return compaction
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
/**
|
|
940
|
-
* Build the ordinary A2 handler table for the built-in agent protocol.
|
|
941
|
-
* Application handlers can be spread beside this table.
|
|
942
|
-
*/
|
|
943
|
-
export function createHandlers<
|
|
944
|
-
M extends UIMessage,
|
|
945
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
946
|
-
T extends ToolSet = ToolSet,
|
|
947
|
-
>(
|
|
948
|
-
options: CreateHandlersOptions<M, D, T>,
|
|
949
|
-
): NonNullable<ServerOptions<D>['handlers']> {
|
|
950
|
-
if (options.model === undefined) {
|
|
951
|
-
throw new TypeError('createHandlers requires model')
|
|
952
|
-
}
|
|
953
|
-
if (
|
|
954
|
-
options.progress?.maxChunks !== undefined &&
|
|
955
|
-
(!Number.isInteger(options.progress.maxChunks) ||
|
|
956
|
-
options.progress.maxChunks < 1)
|
|
957
|
-
) {
|
|
958
|
-
throw new TypeError('progress.maxChunks must be a positive integer')
|
|
959
|
-
}
|
|
960
|
-
if (
|
|
961
|
-
options.progress?.maxDelayMs !== undefined &&
|
|
962
|
-
(!Number.isFinite(options.progress.maxDelayMs) ||
|
|
963
|
-
options.progress.maxDelayMs < 0)
|
|
964
|
-
) {
|
|
965
|
-
throw new TypeError('progress.maxDelayMs must be a non-negative number')
|
|
966
|
-
}
|
|
967
|
-
if (
|
|
968
|
-
options.maxSteps !== undefined &&
|
|
969
|
-
options.maxSteps !== Number.POSITIVE_INFINITY &&
|
|
970
|
-
(!Number.isInteger(options.maxSteps) || options.maxSteps < 1)
|
|
971
|
-
) {
|
|
972
|
-
throw new TypeError('maxSteps must be a positive integer or Infinity')
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
const configuredCompaction =
|
|
976
|
-
options.compaction ?? (options.generate === undefined ? {} : false)
|
|
977
|
-
const staticCompaction =
|
|
978
|
-
typeof configuredCompaction === 'function'
|
|
979
|
-
? undefined
|
|
980
|
-
: validateCompaction({
|
|
981
|
-
compaction: configuredCompaction,
|
|
982
|
-
explicit: options.compaction !== undefined,
|
|
983
|
-
generation: options.generation,
|
|
984
|
-
tools: options.tools,
|
|
985
|
-
})
|
|
986
|
-
|
|
987
|
-
const tools = options.tools ?? ({} as T)
|
|
988
|
-
const generation: AgentGenerationSettings<T> = options.generation ?? {}
|
|
989
|
-
const maxSteps = options.maxSteps ?? Number.POSITIVE_INFINITY
|
|
990
|
-
const control = createControlRuntime({ agent: options.agent })
|
|
991
|
-
const coordinator = control.coordinator
|
|
992
|
-
const contextReducer = createContextReducer({ agent: options.agent })
|
|
993
|
-
const readContext = async (
|
|
994
|
-
session: HandlerContext<D>['session'],
|
|
995
|
-
through: number | 'latest',
|
|
996
|
-
) => {
|
|
997
|
-
const snapshot = await session.state(contextReducer, { through })
|
|
998
|
-
return { state: contextReducer.view(snapshot.state), index: snapshot.index }
|
|
999
|
-
}
|
|
1000
|
-
const promptCache = new Map<string, Promise<ModelMessage[]>>()
|
|
1001
|
-
|
|
1002
|
-
type RuntimeTool = {
|
|
1003
|
-
execute?: (
|
|
1004
|
-
input: unknown,
|
|
1005
|
-
options: {
|
|
1006
|
-
toolCallId: string
|
|
1007
|
-
messages: ModelMessage[]
|
|
1008
|
-
abortSignal?: AbortSignal
|
|
1009
|
-
},
|
|
1010
|
-
) => unknown
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
type ToolHandlerContext = HandlerContext<D, 'ai.tool.execution.requested'>
|
|
1014
|
-
|
|
1015
|
-
const resultEvent = async (
|
|
1016
|
-
call: ToolCalledPayload,
|
|
1017
|
-
suffix: string,
|
|
1018
|
-
result: {
|
|
1019
|
-
output?: unknown
|
|
1020
|
-
error?: string
|
|
1021
|
-
denied?: boolean
|
|
1022
|
-
preliminary?: boolean
|
|
1023
|
-
},
|
|
1024
|
-
): Promise<AppendInput<D>> =>
|
|
1025
|
-
({
|
|
1026
|
-
type: 'ai.tool.result',
|
|
1027
|
-
id: await aiEventId(
|
|
1028
|
-
'a2.ai.tool.result',
|
|
1029
|
-
call.generationId,
|
|
1030
|
-
call.toolCallId,
|
|
1031
|
-
suffix,
|
|
1032
|
-
),
|
|
1033
|
-
payload: {
|
|
1034
|
-
requestId: call.requestId,
|
|
1035
|
-
messageId: call.messageId,
|
|
1036
|
-
generationId: call.generationId,
|
|
1037
|
-
toolCallId: call.toolCallId,
|
|
1038
|
-
toolName: call.toolName,
|
|
1039
|
-
input: call.input,
|
|
1040
|
-
phase: 'execution',
|
|
1041
|
-
...(result.output === undefined ? {} : { output: result.output }),
|
|
1042
|
-
...(result.error === undefined ? {} : { error: result.error }),
|
|
1043
|
-
...(result.denied === undefined ? {} : { denied: result.denied }),
|
|
1044
|
-
...(result.preliminary === undefined
|
|
1045
|
-
? {}
|
|
1046
|
-
: { preliminary: result.preliminary }),
|
|
1047
|
-
...(call.dynamic === undefined ? {} : { dynamic: call.dynamic }),
|
|
1048
|
-
...(call.providerExecuted === undefined
|
|
1049
|
-
? {}
|
|
1050
|
-
: { providerExecuted: call.providerExecuted }),
|
|
1051
|
-
...(call.toolMetadata === undefined
|
|
1052
|
-
? {}
|
|
1053
|
-
: { toolMetadata: call.toolMetadata }),
|
|
1054
|
-
},
|
|
1055
|
-
}) as AppendInput<D>
|
|
1056
|
-
|
|
1057
|
-
const promptMessages = (input: {
|
|
1058
|
-
ctx: ToolHandlerContext
|
|
1059
|
-
call: ToolCalledPayload
|
|
1060
|
-
generation: GenerationStartedPayload
|
|
1061
|
-
}): Promise<ModelMessage[]> => {
|
|
1062
|
-
const { ctx, call, generation: owner } = input
|
|
1063
|
-
const key = promptCacheKey(ctx.event.sessionId, call.generationId)
|
|
1064
|
-
const cached = promptCache.get(key)
|
|
1065
|
-
if (cached) return cached
|
|
1066
|
-
const computation = (async () => {
|
|
1067
|
-
const frontier = owner.promptThroughIndex
|
|
1068
|
-
if (frontier === undefined)
|
|
1069
|
-
throw new TypeError('AI work requires a prompt checkpoint')
|
|
1070
|
-
const snapshot = await readContext(ctx.session, ctx.event.index)
|
|
1071
|
-
if (
|
|
1072
|
-
snapshot.state.generation?.owner.generationId !== owner.generationId ||
|
|
1073
|
-
snapshot.state.generation.owner.requestId !== owner.requestId ||
|
|
1074
|
-
snapshot.state.activeProjection?.generationId !== owner.generationId
|
|
1075
|
-
)
|
|
1076
|
-
throw new TypeError(
|
|
1077
|
-
'Tool prompt checkpoint does not own its generation',
|
|
1078
|
-
)
|
|
1079
|
-
const state = generationBase(snapshot.state)
|
|
1080
|
-
return modelContext({
|
|
1081
|
-
messages: state.messages.filter(
|
|
1082
|
-
(message) => state.messageIndexes[message.id]! <= frontier,
|
|
1083
|
-
),
|
|
1084
|
-
state,
|
|
1085
|
-
tools,
|
|
1086
|
-
})
|
|
1087
|
-
})()
|
|
1088
|
-
promptCache.set(key, computation)
|
|
1089
|
-
void computation.catch(() => {
|
|
1090
|
-
if (promptCache.get(key) === computation) promptCache.delete(key)
|
|
1091
|
-
})
|
|
1092
|
-
return computation
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
const toolExecutionFailure = async (
|
|
1096
|
-
ctx: ToolHandlerContext,
|
|
1097
|
-
call: ToolCalledPayload,
|
|
1098
|
-
error: unknown,
|
|
1099
|
-
): Promise<AppendInput<D> | void> => {
|
|
1100
|
-
const schedulerFailure = consumeSchedulerSendFailure(error)
|
|
1101
|
-
if (checkAbort(ctx.signal)) return
|
|
1102
|
-
if (schedulerFailure === 'retryable') throw error
|
|
1103
|
-
return resultEvent(call, `${ctx.event.id}:execution:error`, {
|
|
1104
|
-
error: errorMessage(error),
|
|
1105
|
-
})
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
const runToolExecution = async (
|
|
1109
|
-
ctx: ToolHandlerContext,
|
|
1110
|
-
call: ToolCalledPayload,
|
|
1111
|
-
execute: NonNullable<RuntimeTool['execute']>,
|
|
1112
|
-
messages: ModelMessage[],
|
|
1113
|
-
): Promise<AppendInput<D> | void> => {
|
|
1114
|
-
let output: unknown
|
|
1115
|
-
try {
|
|
1116
|
-
output = await execute(call.input, {
|
|
1117
|
-
toolCallId: call.toolCallId,
|
|
1118
|
-
messages,
|
|
1119
|
-
abortSignal: ctx.signal,
|
|
1120
|
-
})
|
|
1121
|
-
} catch (error) {
|
|
1122
|
-
return toolExecutionFailure(ctx, call, error)
|
|
1123
|
-
}
|
|
1124
|
-
let iterator: AsyncIterator<unknown> | undefined
|
|
1125
|
-
try {
|
|
1126
|
-
iterator = asyncIterator(output)
|
|
1127
|
-
} catch (error) {
|
|
1128
|
-
return toolExecutionFailure(ctx, call, error)
|
|
1129
|
-
}
|
|
1130
|
-
if (iterator === undefined) {
|
|
1131
|
-
return resultEvent(call, `${ctx.event.id}:execution:0`, { output })
|
|
1132
|
-
}
|
|
1133
|
-
let last: unknown
|
|
1134
|
-
let sequence = 0
|
|
1135
|
-
let done = false
|
|
1136
|
-
try {
|
|
1137
|
-
for (;;) {
|
|
1138
|
-
let result: IteratorResult<unknown>
|
|
1139
|
-
try {
|
|
1140
|
-
result = await iterator.next()
|
|
1141
|
-
} catch (error) {
|
|
1142
|
-
return toolExecutionFailure(ctx, call, error)
|
|
1143
|
-
}
|
|
1144
|
-
if (result.done) {
|
|
1145
|
-
if (ctx.signal.reason instanceof A2Error) throw ctx.signal.reason
|
|
1146
|
-
done = true
|
|
1147
|
-
break
|
|
1148
|
-
}
|
|
1149
|
-
if (checkAbort(ctx.signal)) return
|
|
1150
|
-
last = result.value
|
|
1151
|
-
await control.append({
|
|
1152
|
-
ctx,
|
|
1153
|
-
name: `tool:${call.toolCallId}:preliminary:${sequence}`,
|
|
1154
|
-
events: [
|
|
1155
|
-
await resultEvent(
|
|
1156
|
-
call,
|
|
1157
|
-
`${ctx.event.id}:execution:${ctx.attempt}:${sequence}:preliminary`,
|
|
1158
|
-
{
|
|
1159
|
-
output: result.value,
|
|
1160
|
-
preliminary: true,
|
|
1161
|
-
},
|
|
1162
|
-
),
|
|
1163
|
-
],
|
|
1164
|
-
})
|
|
1165
|
-
sequence += 1
|
|
1166
|
-
}
|
|
1167
|
-
} finally {
|
|
1168
|
-
if (!done) {
|
|
1169
|
-
try {
|
|
1170
|
-
await iterator.return?.()
|
|
1171
|
-
} catch {
|
|
1172
|
-
// Preserve the failure or cancellation that ended execution.
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
return resultEvent(
|
|
1177
|
-
call,
|
|
1178
|
-
`${ctx.event.id}:execution:${sequence}:final`,
|
|
1179
|
-
sequence === 0 ? {} : { output: last },
|
|
1180
|
-
)
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
const executeTool = async (input: {
|
|
1184
|
-
ctx: ToolHandlerContext
|
|
1185
|
-
call: ToolCalledPayload
|
|
1186
|
-
generation: GenerationStartedPayload
|
|
1187
|
-
}): Promise<AppendInput<D> | void> => {
|
|
1188
|
-
const { ctx, call } = input
|
|
1189
|
-
const tool = tools[call.toolName] as RuntimeTool | undefined
|
|
1190
|
-
const execute = tool?.execute
|
|
1191
|
-
if (execute === undefined) {
|
|
1192
|
-
return resultEvent(call, `${ctx.event.id}:execution:error`, {
|
|
1193
|
-
error: `Tool '${call.toolName}' has no server executor`,
|
|
1194
|
-
})
|
|
1195
|
-
}
|
|
1196
|
-
const messages = await promptMessages(input)
|
|
1197
|
-
if (checkAbort(ctx.signal)) return
|
|
1198
|
-
const scope: AmbientToolScope = {
|
|
1199
|
-
contract: options.agent.contract,
|
|
1200
|
-
context: ctx,
|
|
1201
|
-
}
|
|
1202
|
-
// Async iteration resumes in the caller's context, so the whole
|
|
1203
|
-
// execution, not just the execute call, runs inside the scope.
|
|
1204
|
-
return toolScopeStorage.run(scope, () =>
|
|
1205
|
-
runToolExecution(ctx, call, execute, messages),
|
|
1206
|
-
)
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
const handleToolExecution = async (
|
|
1210
|
-
ctx: ToolHandlerContext,
|
|
1211
|
-
): Promise<AppendInput<D>> => {
|
|
1212
|
-
try {
|
|
1213
|
-
const state = (
|
|
1214
|
-
await ctx.session.state(control.reducer, { through: 'latest' })
|
|
1215
|
-
).state
|
|
1216
|
-
const request = ctx.event.payload
|
|
1217
|
-
const call = state.coordinator.response?.calls.find(
|
|
1218
|
-
(candidate) => candidate.work?.id === ctx.event.id,
|
|
1219
|
-
)
|
|
1220
|
-
if (
|
|
1221
|
-
state.active?.turnId !== request.turnId ||
|
|
1222
|
-
Object.values(state.pendingStops).includes(request.turnId) ||
|
|
1223
|
-
state.active.suspended ||
|
|
1224
|
-
state.active.version !== request.version ||
|
|
1225
|
-
state.coordinator.response?.failure !== undefined ||
|
|
1226
|
-
!call ||
|
|
1227
|
-
call.terminal ||
|
|
1228
|
-
call.work?.settled
|
|
1229
|
-
)
|
|
1230
|
-
return control.settled({ ctx, events: undefined })
|
|
1231
|
-
return control.settled({
|
|
1232
|
-
ctx,
|
|
1233
|
-
events: await executeTool({
|
|
1234
|
-
ctx,
|
|
1235
|
-
call: request.call,
|
|
1236
|
-
generation: request.generation,
|
|
1237
|
-
}),
|
|
1238
|
-
})
|
|
1239
|
-
} catch (error) {
|
|
1240
|
-
if (control.cancelled({ error, signal: ctx.signal }))
|
|
1241
|
-
return control.settled({ ctx, events: undefined })
|
|
1242
|
-
throw error
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
const generationHandler = async (
|
|
1247
|
-
ctx: HandlerContext<D, 'ai.generation.requested'>,
|
|
1248
|
-
): Promise<void | AppendInput<D> | readonly AppendInput<D>[]> => {
|
|
1249
|
-
if (checkAbort(ctx.signal)) return
|
|
1250
|
-
const requestId = ctx.event.id
|
|
1251
|
-
const request = ctx.event.payload
|
|
1252
|
-
const snapshot = await readContext(ctx.session, 'latest')
|
|
1253
|
-
if (
|
|
1254
|
-
snapshot.state.closed ||
|
|
1255
|
-
snapshot.state.authority?.requestId !== requestId ||
|
|
1256
|
-
snapshot.state.authority?.phase === 'paused' ||
|
|
1257
|
-
snapshot.state.authority?.phase === 'pausing'
|
|
1258
|
-
)
|
|
1259
|
-
return
|
|
1260
|
-
const controlState = (
|
|
1261
|
-
await ctx.session.state(control.reducer, { through: snapshot.index })
|
|
1262
|
-
).state
|
|
1263
|
-
if (
|
|
1264
|
-
request.control !== undefined &&
|
|
1265
|
-
Object.values(controlState.pendingStops).includes(request.control.turnId)
|
|
1266
|
-
)
|
|
1267
|
-
return
|
|
1268
|
-
const coordinatorState = controlState.coordinator
|
|
1269
|
-
if (
|
|
1270
|
-
coordinatorState.closed ||
|
|
1271
|
-
coordinatorState.response?.activeRequestId !== requestId
|
|
1272
|
-
)
|
|
1273
|
-
return
|
|
1274
|
-
const response = coordinatorState.response
|
|
1275
|
-
const current =
|
|
1276
|
-
response.generation?.requestId === requestId
|
|
1277
|
-
? response.generation
|
|
1278
|
-
: undefined
|
|
1279
|
-
if (
|
|
1280
|
-
current !== undefined &&
|
|
1281
|
-
(response.completion !== undefined ||
|
|
1282
|
-
(response.failure !== undefined &&
|
|
1283
|
-
response.failure.superseded !== true))
|
|
1284
|
-
)
|
|
1285
|
-
return
|
|
1286
|
-
if (current !== undefined && current.attempt >= ctx.attempt) return
|
|
1287
|
-
const attempt = ctx.attempt
|
|
1288
|
-
const generationId = await aiEventId(
|
|
1289
|
-
'a2.ai.generation',
|
|
1290
|
-
requestId,
|
|
1291
|
-
String(attempt),
|
|
1292
|
-
)
|
|
1293
|
-
const responseMessageId = request.responseMessageId
|
|
1294
|
-
const responseStepCount = response.stepCount
|
|
1295
|
-
if (
|
|
1296
|
-
request.reason === 'tool' &&
|
|
1297
|
-
(request.sourceGenerationId !==
|
|
1298
|
-
response.source?.generation.generationId ||
|
|
1299
|
-
response.responseMessageId !== request.messageId ||
|
|
1300
|
-
response.responseMessageId !== request.responseMessageId)
|
|
1301
|
-
)
|
|
1302
|
-
return
|
|
1303
|
-
const previous = current
|
|
1304
|
-
const state =
|
|
1305
|
-
previous === undefined
|
|
1306
|
-
? snapshot.state
|
|
1307
|
-
: contextForAttempt(snapshot.state)
|
|
1308
|
-
const resolverContext: AgentResolverContext<M, D> = {
|
|
1309
|
-
event: ctx.event,
|
|
1310
|
-
messages: contextMessages({ state, coordinator: coordinatorState }),
|
|
1311
|
-
session: {
|
|
1312
|
-
state: (reducer, readOptions) =>
|
|
1313
|
-
ctx.session.state(reducer, {
|
|
1314
|
-
...readOptions,
|
|
1315
|
-
through: readOptions?.through ?? snapshot.index,
|
|
1316
|
-
}),
|
|
1317
|
-
},
|
|
1318
|
-
signal: ctx.signal,
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
let generationStarted = false
|
|
1322
|
-
try {
|
|
1323
|
-
const resolvedModel = await resolve(options.model, resolverContext)
|
|
1324
|
-
if (resolvedModel === undefined) {
|
|
1325
|
-
throw new TypeError('the model resolver returned undefined')
|
|
1326
|
-
}
|
|
1327
|
-
if (checkAbort(ctx.signal)) return
|
|
1328
|
-
const resolvedInstructions =
|
|
1329
|
-
options.instructions === undefined
|
|
1330
|
-
? undefined
|
|
1331
|
-
: await resolve(options.instructions, resolverContext)
|
|
1332
|
-
if (checkAbort(ctx.signal)) return
|
|
1333
|
-
const compaction =
|
|
1334
|
-
typeof configuredCompaction === 'function'
|
|
1335
|
-
? validateCompaction({
|
|
1336
|
-
compaction: await resolve(configuredCompaction, resolverContext),
|
|
1337
|
-
explicit: true,
|
|
1338
|
-
generation: options.generation,
|
|
1339
|
-
tools: options.tools,
|
|
1340
|
-
})
|
|
1341
|
-
: staticCompaction!
|
|
1342
|
-
if (checkAbort(ctx.signal)) return
|
|
1343
|
-
|
|
1344
|
-
if (request.reason === 'tool' && responseStepCount >= maxSteps) {
|
|
1345
|
-
const source = response.source?.generation
|
|
1346
|
-
if (source === undefined) return
|
|
1347
|
-
return {
|
|
1348
|
-
type: 'ai.generation.failed',
|
|
1349
|
-
id: await aiEventId('a2.ai.step-limit', requestId),
|
|
1350
|
-
payload: {
|
|
1351
|
-
requestId: source.requestId,
|
|
1352
|
-
messageId: source.messageId,
|
|
1353
|
-
generationId: source.generationId,
|
|
1354
|
-
responseMessageId,
|
|
1355
|
-
error: `agent exceeded the ${maxSteps}-step limit`,
|
|
1356
|
-
stepLimit: true,
|
|
1357
|
-
},
|
|
1358
|
-
} as AppendInput<D>
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
const started: GenerationStartedPayload = {
|
|
1362
|
-
requestId,
|
|
1363
|
-
messageId: request.messageId,
|
|
1364
|
-
generationId,
|
|
1365
|
-
responseMessageId,
|
|
1366
|
-
attempt,
|
|
1367
|
-
model: modelName(resolvedModel),
|
|
1368
|
-
promptThroughIndex: snapshot.index,
|
|
1369
|
-
}
|
|
1370
|
-
const catalogModelId = gatewayModelId(resolvedModel)
|
|
1371
|
-
const gatewayOptions = options.generation?.providerOptions?.['gateway']
|
|
1372
|
-
const usesFallbackModels =
|
|
1373
|
-
Array.isArray(gatewayOptions?.['models']) &&
|
|
1374
|
-
gatewayOptions['models'].length > 0
|
|
1375
|
-
const discoversMetadata =
|
|
1376
|
-
compaction !== false &&
|
|
1377
|
-
!('shouldCompact' in compaction) &&
|
|
1378
|
-
compaction.thresholdTokens === undefined &&
|
|
1379
|
-
!usesFallbackModels &&
|
|
1380
|
-
catalogModelId !== undefined
|
|
1381
|
-
const startEvents: AppendInput<D>[] = []
|
|
1382
|
-
if (
|
|
1383
|
-
discoversMetadata &&
|
|
1384
|
-
!Object.hasOwn(state.modelMetadata, catalogModelId)
|
|
1385
|
-
) {
|
|
1386
|
-
startEvents.push({
|
|
1387
|
-
type: 'ai.model.metadata.requested',
|
|
1388
|
-
id: await aiEventId(
|
|
1389
|
-
'a2.ai.model.metadata',
|
|
1390
|
-
options.agent.contract.name,
|
|
1391
|
-
ctx.event.sessionId,
|
|
1392
|
-
catalogModelId,
|
|
1393
|
-
),
|
|
1394
|
-
payload: { modelId: catalogModelId },
|
|
1395
|
-
} as AppendInput<D>)
|
|
1396
|
-
}
|
|
1397
|
-
if (previous) {
|
|
1398
|
-
const payload = previous
|
|
1399
|
-
const superseded: GenerationFailedPayload = {
|
|
1400
|
-
requestId,
|
|
1401
|
-
messageId: payload.messageId,
|
|
1402
|
-
generationId: payload.generationId,
|
|
1403
|
-
responseMessageId: payload.responseMessageId,
|
|
1404
|
-
error: 'generation attempt was superseded after an incomplete run',
|
|
1405
|
-
superseded: true,
|
|
1406
|
-
}
|
|
1407
|
-
startEvents.push({
|
|
1408
|
-
type: 'ai.generation.failed',
|
|
1409
|
-
id: await aiEventId('a2.ai.superseded', payload.generationId),
|
|
1410
|
-
payload: superseded,
|
|
1411
|
-
} as AppendInput<D>)
|
|
1412
|
-
}
|
|
1413
|
-
startEvents.push({
|
|
1414
|
-
type: 'ai.generation.started',
|
|
1415
|
-
id: generationId,
|
|
1416
|
-
payload: started,
|
|
1417
|
-
} as AppendInput<D>)
|
|
1418
|
-
const generationEvents = await control.append({
|
|
1419
|
-
ctx,
|
|
1420
|
-
name: 'generation-start',
|
|
1421
|
-
events: startEvents,
|
|
1422
|
-
})
|
|
1423
|
-
generationStarted = true
|
|
1424
|
-
|
|
1425
|
-
if (checkAbort(ctx.signal)) return
|
|
1426
|
-
|
|
1427
|
-
const promptCoordinatorState = coordinatorState
|
|
1428
|
-
const messages = contextMessages({
|
|
1429
|
-
state,
|
|
1430
|
-
coordinator: promptCoordinatorState,
|
|
1431
|
-
})
|
|
1432
|
-
let generationMessages = messages
|
|
1433
|
-
let modelMessages = await modelContext({ messages, state, tools })
|
|
1434
|
-
const baseContext: AgentGenerateContext<M, D, T> = {
|
|
1435
|
-
request: ctx.event,
|
|
1436
|
-
requestId,
|
|
1437
|
-
generationId,
|
|
1438
|
-
responseMessageId,
|
|
1439
|
-
messages,
|
|
1440
|
-
modelMessages,
|
|
1441
|
-
session: resolverContext.session,
|
|
1442
|
-
signal: ctx.signal,
|
|
1443
|
-
model: resolvedModel,
|
|
1444
|
-
tools,
|
|
1445
|
-
...(resolvedInstructions === undefined
|
|
1446
|
-
? {}
|
|
1447
|
-
: { instructions: resolvedInstructions }),
|
|
1448
|
-
generation,
|
|
1449
|
-
}
|
|
1450
|
-
const policy = compaction
|
|
1451
|
-
const tracksInput =
|
|
1452
|
-
policy !== false &&
|
|
1453
|
-
!('shouldCompact' in policy) &&
|
|
1454
|
-
(policy.thresholdTokens !== undefined || discoversMetadata)
|
|
1455
|
-
const metadata =
|
|
1456
|
-
catalogModelId === undefined
|
|
1457
|
-
? undefined
|
|
1458
|
-
: state.modelMetadata[catalogModelId]
|
|
1459
|
-
const limits =
|
|
1460
|
-
metadata?.status === 'resolved' && !usesFallbackModels
|
|
1461
|
-
? metadata.limits
|
|
1462
|
-
: undefined
|
|
1463
|
-
let inputTokenEstimate: number | undefined
|
|
1464
|
-
let compacted = false
|
|
1465
|
-
let compactionEstimate:
|
|
1466
|
-
| Pick<
|
|
1467
|
-
CompactionRequestedPayload,
|
|
1468
|
-
'inputTokenEstimate' | 'inputTokens' | 'thresholdTokens'
|
|
1469
|
-
>
|
|
1470
|
-
| undefined
|
|
1471
|
-
const hasPendingTools = messages.some((message) =>
|
|
1472
|
-
message.parts.some(
|
|
1473
|
-
(part) =>
|
|
1474
|
-
isToolUIPart(part) &&
|
|
1475
|
-
!(
|
|
1476
|
-
part.state === 'output-error' ||
|
|
1477
|
-
part.state === 'output-denied' ||
|
|
1478
|
-
(part.state === 'output-available' && part.preliminary !== true)
|
|
1479
|
-
),
|
|
1480
|
-
),
|
|
1481
|
-
)
|
|
1482
|
-
if (policy && !hasPendingTools) {
|
|
1483
|
-
const compactionContext = {
|
|
1484
|
-
...resolverContext,
|
|
1485
|
-
messages,
|
|
1486
|
-
modelMessages,
|
|
1487
|
-
}
|
|
1488
|
-
let shouldCompact: boolean
|
|
1489
|
-
if ('shouldCompact' in policy) {
|
|
1490
|
-
shouldCompact = await policy.shouldCompact(compactionContext)
|
|
1491
|
-
} else if (tracksInput) {
|
|
1492
|
-
inputTokenEstimate = await estimateInputTokens({
|
|
1493
|
-
messages: modelMessages,
|
|
1494
|
-
instructions: resolvedInstructions,
|
|
1495
|
-
tools,
|
|
1496
|
-
})
|
|
1497
|
-
const inputTokens = measuredInputTokens({
|
|
1498
|
-
estimate: inputTokenEstimate,
|
|
1499
|
-
model: modelName(resolvedModel),
|
|
1500
|
-
calibration: coordinatorState.calibration,
|
|
1501
|
-
})
|
|
1502
|
-
const threshold =
|
|
1503
|
-
policy.thresholdTokens ??
|
|
1504
|
-
(limits === undefined
|
|
1505
|
-
? undefined
|
|
1506
|
-
: Math.floor(
|
|
1507
|
-
Math.min(
|
|
1508
|
-
limits.contextWindow * 0.75,
|
|
1509
|
-
limits.contextWindow - (generation.maxOutputTokens ?? 0),
|
|
1510
|
-
),
|
|
1511
|
-
))
|
|
1512
|
-
if (
|
|
1513
|
-
limits !== undefined &&
|
|
1514
|
-
threshold !== undefined &&
|
|
1515
|
-
threshold <= 0
|
|
1516
|
-
) {
|
|
1517
|
-
throw new Error(
|
|
1518
|
-
`output allowance exhausts the ${limits.contextWindow}-token context window`,
|
|
1519
|
-
)
|
|
1520
|
-
}
|
|
1521
|
-
shouldCompact = threshold !== undefined && inputTokens >= threshold
|
|
1522
|
-
if (threshold !== undefined && shouldCompact) {
|
|
1523
|
-
compactionEstimate = {
|
|
1524
|
-
inputTokenEstimate,
|
|
1525
|
-
inputTokens,
|
|
1526
|
-
thresholdTokens: threshold,
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
} else {
|
|
1530
|
-
shouldCompact = false
|
|
1531
|
-
}
|
|
1532
|
-
if (checkAbort(ctx.signal)) return
|
|
1533
|
-
if (shouldCompact) {
|
|
1534
|
-
const latest = (
|
|
1535
|
-
await ctx.session.state(coordinator, { through: 'latest' })
|
|
1536
|
-
).state
|
|
1537
|
-
shouldCompact = Object.keys(latest.unsettledTools ?? {}).length === 0
|
|
1538
|
-
}
|
|
1539
|
-
if (checkAbort(ctx.signal)) return
|
|
1540
|
-
if (shouldCompact) {
|
|
1541
|
-
const throughMessageId =
|
|
1542
|
-
state.messages.findLast(
|
|
1543
|
-
(message) =>
|
|
1544
|
-
!promptCoordinatorState.queued.some(
|
|
1545
|
-
(queued) => queued.messageId === message.id,
|
|
1546
|
-
),
|
|
1547
|
-
)?.id ?? request.messageId
|
|
1548
|
-
const throughIndex = snapshot.index
|
|
1549
|
-
generationEvents.push(
|
|
1550
|
-
...(await control.append({
|
|
1551
|
-
ctx,
|
|
1552
|
-
name: 'compaction-requested',
|
|
1553
|
-
events: [
|
|
1554
|
-
{
|
|
1555
|
-
type: 'ai.compaction.requested',
|
|
1556
|
-
id: await aiEventId(
|
|
1557
|
-
'a2.ai.compaction.requested',
|
|
1558
|
-
generationId,
|
|
1559
|
-
),
|
|
1560
|
-
payload: {
|
|
1561
|
-
generationId,
|
|
1562
|
-
throughMessageId,
|
|
1563
|
-
throughIndex,
|
|
1564
|
-
...compactionEstimate,
|
|
1565
|
-
},
|
|
1566
|
-
} as AppendInput<D>,
|
|
1567
|
-
],
|
|
1568
|
-
})),
|
|
1569
|
-
)
|
|
1570
|
-
const result = !('shouldCompact' in policy)
|
|
1571
|
-
? {
|
|
1572
|
-
messages: [] as M[],
|
|
1573
|
-
...(await summarize({
|
|
1574
|
-
agentName: options.agent.contract.name,
|
|
1575
|
-
context: baseContext,
|
|
1576
|
-
instructions: policy.instructions,
|
|
1577
|
-
})),
|
|
1578
|
-
}
|
|
1579
|
-
: { messages: await policy.compact(compactionContext) }
|
|
1580
|
-
if (checkAbort(ctx.signal)) return
|
|
1581
|
-
const completed: CompactionCompletedPayload<M> = {
|
|
1582
|
-
generationId,
|
|
1583
|
-
throughMessageId,
|
|
1584
|
-
throughIndex,
|
|
1585
|
-
...result,
|
|
1586
|
-
...(promptCoordinatorState.queued.length === 0
|
|
1587
|
-
? {}
|
|
1588
|
-
: {
|
|
1589
|
-
retainedMessageIds: promptCoordinatorState.queued.map(
|
|
1590
|
-
(item) => item.messageId,
|
|
1591
|
-
),
|
|
1592
|
-
}),
|
|
1593
|
-
}
|
|
1594
|
-
generationEvents.push(
|
|
1595
|
-
...(await control.append({
|
|
1596
|
-
ctx,
|
|
1597
|
-
name: 'compaction-completed',
|
|
1598
|
-
events: [
|
|
1599
|
-
{
|
|
1600
|
-
type: 'ai.compaction.completed',
|
|
1601
|
-
id: await aiEventId(
|
|
1602
|
-
'a2.ai.compaction.completed',
|
|
1603
|
-
generationId,
|
|
1604
|
-
),
|
|
1605
|
-
payload: completed,
|
|
1606
|
-
} as AppendInput<D>,
|
|
1607
|
-
],
|
|
1608
|
-
})),
|
|
1609
|
-
)
|
|
1610
|
-
const queued = new Set(
|
|
1611
|
-
promptCoordinatorState.queued.map((item) => item.messageId),
|
|
1612
|
-
)
|
|
1613
|
-
generationMessages = result.messages.filter(
|
|
1614
|
-
(message) => !queued.has(message.id),
|
|
1615
|
-
)
|
|
1616
|
-
compacted = true
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
const currentState = foldAIEvents({
|
|
1621
|
-
reducer: contextReducer[aiStateReducer],
|
|
1622
|
-
events: generationEvents,
|
|
1623
|
-
state,
|
|
1624
|
-
})
|
|
1625
|
-
if (request.reason === 'retry' || previous !== undefined) {
|
|
1626
|
-
generationMessages = contextMessages({
|
|
1627
|
-
state: currentState,
|
|
1628
|
-
coordinator: promptCoordinatorState,
|
|
1629
|
-
})
|
|
1630
|
-
}
|
|
1631
|
-
if (
|
|
1632
|
-
(policy && 'shouldCompact' in policy) ||
|
|
1633
|
-
generationMessages !== messages ||
|
|
1634
|
-
activeCompaction(currentState)?.summary !==
|
|
1635
|
-
activeCompaction(state)?.summary
|
|
1636
|
-
) {
|
|
1637
|
-
modelMessages = await modelContext({
|
|
1638
|
-
messages: generationMessages,
|
|
1639
|
-
state: currentState,
|
|
1640
|
-
tools,
|
|
1641
|
-
})
|
|
1642
|
-
}
|
|
1643
|
-
promptCache.set(
|
|
1644
|
-
promptCacheKey(ctx.event.sessionId, generationId),
|
|
1645
|
-
Promise.resolve(modelMessages),
|
|
1646
|
-
)
|
|
1647
|
-
if (tracksInput && (inputTokenEstimate === undefined || compacted)) {
|
|
1648
|
-
inputTokenEstimate = await estimateInputTokens({
|
|
1649
|
-
messages: modelMessages,
|
|
1650
|
-
instructions: resolvedInstructions,
|
|
1651
|
-
tools,
|
|
1652
|
-
})
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
let pendingToolCalls: PendingToolCall[] = []
|
|
1656
|
-
if (checkAbort(ctx.signal)) return
|
|
1657
|
-
const generateContext: AgentGenerateContext<M, D, T> = {
|
|
1658
|
-
request: ctx.event,
|
|
1659
|
-
requestId,
|
|
1660
|
-
generationId,
|
|
1661
|
-
responseMessageId,
|
|
1662
|
-
messages: generationMessages,
|
|
1663
|
-
modelMessages,
|
|
1664
|
-
session: resolverContext.session,
|
|
1665
|
-
signal: ctx.signal,
|
|
1666
|
-
model: resolvedModel,
|
|
1667
|
-
tools,
|
|
1668
|
-
...(resolvedInstructions === undefined
|
|
1669
|
-
? {}
|
|
1670
|
-
: { instructions: resolvedInstructions }),
|
|
1671
|
-
generation: options.generation ?? {},
|
|
1672
|
-
}
|
|
1673
|
-
const custom = options.generate !== undefined
|
|
1674
|
-
const source =
|
|
1675
|
-
options.generate === undefined
|
|
1676
|
-
? await generateWithAISDK({
|
|
1677
|
-
agentName: options.agent.contract.name,
|
|
1678
|
-
context: generateContext,
|
|
1679
|
-
messageMetadata: options.messageMetadata,
|
|
1680
|
-
})
|
|
1681
|
-
: { stream: await options.generate(generateContext) }
|
|
1682
|
-
const updates = consumeGeneration({
|
|
1683
|
-
source,
|
|
1684
|
-
...(options.progress === undefined
|
|
1685
|
-
? {}
|
|
1686
|
-
: { progress: options.progress }),
|
|
1687
|
-
})
|
|
1688
|
-
|
|
1689
|
-
let sequence = 0
|
|
1690
|
-
let finish: AgentGenerationFinish | undefined
|
|
1691
|
-
for await (const update of updates) {
|
|
1692
|
-
ctx.signal.throwIfAborted()
|
|
1693
|
-
if (update.type === 'finish') {
|
|
1694
|
-
finish = update
|
|
1695
|
-
continue
|
|
1696
|
-
}
|
|
1697
|
-
const progress: GenerationProgressPayload = {
|
|
1698
|
-
requestId,
|
|
1699
|
-
messageId: request.messageId,
|
|
1700
|
-
generationId,
|
|
1701
|
-
responseMessageId,
|
|
1702
|
-
sequence,
|
|
1703
|
-
chunks: update.chunks,
|
|
1704
|
-
}
|
|
1705
|
-
const lifecycle = await lifecycleEvents<D, T>({
|
|
1706
|
-
requestId,
|
|
1707
|
-
messageId: responseMessageId,
|
|
1708
|
-
generationId,
|
|
1709
|
-
sequence,
|
|
1710
|
-
chunks: update.chunks,
|
|
1711
|
-
pending: pendingToolCalls,
|
|
1712
|
-
tools,
|
|
1713
|
-
generation,
|
|
1714
|
-
custom,
|
|
1715
|
-
})
|
|
1716
|
-
pendingToolCalls = lifecycle.pending
|
|
1717
|
-
await control.append({
|
|
1718
|
-
ctx,
|
|
1719
|
-
name: `generation-progress:${sequence}`,
|
|
1720
|
-
events: [
|
|
1721
|
-
{
|
|
1722
|
-
type: 'ai.generation.progress',
|
|
1723
|
-
id: await aiEventId(
|
|
1724
|
-
'a2.ai.progress',
|
|
1725
|
-
generationId,
|
|
1726
|
-
String(sequence),
|
|
1727
|
-
),
|
|
1728
|
-
payload: progress,
|
|
1729
|
-
} as AppendInput<D>,
|
|
1730
|
-
...lifecycle.events,
|
|
1731
|
-
],
|
|
1732
|
-
})
|
|
1733
|
-
sequence += 1
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
if (!finish) throw new Error('agent generation finished without output')
|
|
1737
|
-
const completed: GenerationCompletedPayload = {
|
|
1738
|
-
requestId,
|
|
1739
|
-
messageId: request.messageId,
|
|
1740
|
-
generationId,
|
|
1741
|
-
responseMessageId,
|
|
1742
|
-
...(inputTokenEstimate === undefined ? {} : { inputTokenEstimate }),
|
|
1743
|
-
...(finish.finishReason === undefined
|
|
1744
|
-
? {}
|
|
1745
|
-
: { finishReason: finish.finishReason }),
|
|
1746
|
-
...(finish.usage === undefined ? {} : { usage: finish.usage }),
|
|
1747
|
-
}
|
|
1748
|
-
const completionEvent = {
|
|
1749
|
-
type: 'ai.generation.completed',
|
|
1750
|
-
id: await aiEventId('a2.ai.completed', generationId),
|
|
1751
|
-
payload: completed,
|
|
1752
|
-
} as AppendInput<D>
|
|
1753
|
-
const unresolved = options.generate
|
|
1754
|
-
? pendingToolCalls.filter(
|
|
1755
|
-
(pending) => pending.classification !== 'automatic',
|
|
1756
|
-
)
|
|
1757
|
-
: []
|
|
1758
|
-
if (unresolved.length > 0) {
|
|
1759
|
-
throw new Error(
|
|
1760
|
-
`generation ended with unresolved tool authorization for '${unresolved[0]!.call.toolName}'`,
|
|
1761
|
-
)
|
|
1762
|
-
}
|
|
1763
|
-
const pendingEvents = await Promise.all(
|
|
1764
|
-
pendingToolCalls
|
|
1765
|
-
.filter(
|
|
1766
|
-
(pending) =>
|
|
1767
|
-
pending.classification === 'automatic' ||
|
|
1768
|
-
(options.generate === undefined &&
|
|
1769
|
-
pending.classification === 'unknown'),
|
|
1770
|
-
)
|
|
1771
|
-
.map((pending) => toolCalledEvent<D>(pending.call)),
|
|
1772
|
-
)
|
|
1773
|
-
if (finish.finishReason === 'tool-calls') {
|
|
1774
|
-
return [...pendingEvents, completionEvent]
|
|
1775
|
-
}
|
|
1776
|
-
return [
|
|
1777
|
-
...pendingEvents,
|
|
1778
|
-
completionEvent,
|
|
1779
|
-
{
|
|
1780
|
-
type: 'ai.message.completed',
|
|
1781
|
-
id: await aiEventId('a2.ai.message.completed', generationId),
|
|
1782
|
-
payload: {
|
|
1783
|
-
messageId: responseMessageId,
|
|
1784
|
-
} satisfies MessageCompletedPayload,
|
|
1785
|
-
} as AppendInput<D>,
|
|
1786
|
-
]
|
|
1787
|
-
} catch (error) {
|
|
1788
|
-
if (error instanceof ControlCancelled) throw error
|
|
1789
|
-
if (error instanceof A2Error) {
|
|
1790
|
-
checkAbort(ctx.signal)
|
|
1791
|
-
throw error
|
|
1792
|
-
}
|
|
1793
|
-
if (checkAbort(ctx.signal)) return
|
|
1794
|
-
if (!generationStarted) throw error
|
|
1795
|
-
const failed: GenerationFailedPayload = {
|
|
1796
|
-
requestId,
|
|
1797
|
-
messageId: request.messageId,
|
|
1798
|
-
generationId,
|
|
1799
|
-
responseMessageId,
|
|
1800
|
-
error: errorMessage(error),
|
|
1801
|
-
}
|
|
1802
|
-
return {
|
|
1803
|
-
type: 'ai.generation.failed',
|
|
1804
|
-
id: await aiEventId('a2.ai.failed', generationId),
|
|
1805
|
-
payload: failed,
|
|
1806
|
-
} as AppendInput<D>
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
const clearPromptCache = (sessionId: string): void => {
|
|
1811
|
-
const prefix = `${sessionId}\u001f`
|
|
1812
|
-
for (const key of promptCache.keys()) {
|
|
1813
|
-
if (key.startsWith(prefix)) promptCache.delete(key)
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
return {
|
|
1818
|
-
'ai.model.metadata.requested': {
|
|
1819
|
-
handler: async (ctx) => {
|
|
1820
|
-
const limits = await readModelLimits({
|
|
1821
|
-
modelId: ctx.event.payload.modelId,
|
|
1822
|
-
signal: ctx.signal,
|
|
1823
|
-
})
|
|
1824
|
-
ctx.signal.throwIfAborted()
|
|
1825
|
-
return {
|
|
1826
|
-
type: 'ai.model.metadata.resolved',
|
|
1827
|
-
id: await aiEventId('a2.ai.model.metadata.resolved', ctx.event.id),
|
|
1828
|
-
payload: { modelId: ctx.event.payload.modelId, limits },
|
|
1829
|
-
} as AppendInput<D>
|
|
1830
|
-
},
|
|
1831
|
-
},
|
|
1832
|
-
'ai.control.requested': {
|
|
1833
|
-
lane: ({ event }) => {
|
|
1834
|
-
const command = event.payload as ControlCommand<M>
|
|
1835
|
-
if (event.id === '')
|
|
1836
|
-
throw new A2Error(
|
|
1837
|
-
'INVALID_PAYLOAD',
|
|
1838
|
-
'AI command IDs must be nonempty',
|
|
1839
|
-
)
|
|
1840
|
-
if (event.id?.startsWith(AI_EVENT_ID_PREFIX))
|
|
1841
|
-
throw new A2Error(
|
|
1842
|
-
'INVALID_PAYLOAD',
|
|
1843
|
-
`AI command IDs cannot use the reserved '${AI_EVENT_ID_PREFIX}' prefix`,
|
|
1844
|
-
)
|
|
1845
|
-
if (
|
|
1846
|
-
(command.action === 'send' || command.action === 'steer') &&
|
|
1847
|
-
event.id !== command.message.id
|
|
1848
|
-
)
|
|
1849
|
-
throw new A2Error(
|
|
1850
|
-
'INVALID_PAYLOAD',
|
|
1851
|
-
'AI input event ID must equal message.id',
|
|
1852
|
-
)
|
|
1853
|
-
return 'a2.ai.control'
|
|
1854
|
-
},
|
|
1855
|
-
handler: control.handler,
|
|
1856
|
-
},
|
|
1857
|
-
'ai.work.reported': { lane: 'a2.ai.control', handler: control.handler },
|
|
1858
|
-
'ai.message.completed': {
|
|
1859
|
-
handler: async (ctx) => {
|
|
1860
|
-
clearPromptCache(ctx.event.sessionId)
|
|
1861
|
-
},
|
|
1862
|
-
},
|
|
1863
|
-
'ai.message.interrupted': {
|
|
1864
|
-
handler: async (ctx) => {
|
|
1865
|
-
clearPromptCache(ctx.event.sessionId)
|
|
1866
|
-
},
|
|
1867
|
-
},
|
|
1868
|
-
'ai.session.closed': {
|
|
1869
|
-
lane: 'a2.ai.control',
|
|
1870
|
-
handler: async (ctx) => {
|
|
1871
|
-
clearPromptCache(ctx.event.sessionId)
|
|
1872
|
-
return control.handler(ctx)
|
|
1873
|
-
},
|
|
1874
|
-
},
|
|
1875
|
-
'ai.generation.failed': {
|
|
1876
|
-
handler: (ctx) => {
|
|
1877
|
-
promptCache.delete(
|
|
1878
|
-
promptCacheKey(ctx.event.sessionId, ctx.event.payload.generationId),
|
|
1879
|
-
)
|
|
1880
|
-
return Promise.resolve()
|
|
1881
|
-
},
|
|
1882
|
-
},
|
|
1883
|
-
'ai.generation.requested': {
|
|
1884
|
-
lane: 'a2.ai.model',
|
|
1885
|
-
abortOn: {
|
|
1886
|
-
'ai.control.requested': (event, trigger) =>
|
|
1887
|
-
event.payload.action === 'stop' &&
|
|
1888
|
-
event.payload.turnId === trigger.payload.control?.turnId,
|
|
1889
|
-
'ai.control.committed': (event, trigger) => {
|
|
1890
|
-
const active = (event.payload as ControlCommit).view
|
|
1891
|
-
return (
|
|
1892
|
-
active?.turnId !== trigger.payload.control?.turnId ||
|
|
1893
|
-
active?.version !== trigger.payload.control?.version
|
|
1894
|
-
)
|
|
1895
|
-
},
|
|
1896
|
-
'ai.session.closed': true,
|
|
1897
|
-
},
|
|
1898
|
-
handler: async (ctx) => {
|
|
1899
|
-
try {
|
|
1900
|
-
return control.settled({ ctx, events: await generationHandler(ctx) })
|
|
1901
|
-
} catch (error) {
|
|
1902
|
-
if (control.cancelled({ error, signal: ctx.signal }))
|
|
1903
|
-
return control.settled({ ctx, events: undefined })
|
|
1904
|
-
throw error
|
|
1905
|
-
}
|
|
1906
|
-
},
|
|
1907
|
-
},
|
|
1908
|
-
'ai.tool.execution.requested': {
|
|
1909
|
-
abortOn: {
|
|
1910
|
-
'ai.control.requested': (event, trigger) =>
|
|
1911
|
-
event.payload.action === 'stop' &&
|
|
1912
|
-
event.payload.turnId === trigger.payload.turnId,
|
|
1913
|
-
'ai.generation.failed': (event, trigger) =>
|
|
1914
|
-
event.payload.generationId === trigger.payload.call.generationId,
|
|
1915
|
-
'ai.control.committed': (event, trigger) => {
|
|
1916
|
-
const active = (event.payload as ControlCommit).view
|
|
1917
|
-
return (
|
|
1918
|
-
active?.turnId !== trigger.payload.turnId ||
|
|
1919
|
-
active?.version !== trigger.payload.version
|
|
1920
|
-
)
|
|
1921
|
-
},
|
|
1922
|
-
'ai.session.closed': true,
|
|
1923
|
-
},
|
|
1924
|
-
handler: handleToolExecution,
|
|
1925
|
-
},
|
|
1926
|
-
} as NonNullable<ServerOptions<D>['handlers']>
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
/** Assemble an A2 server with the built-in agent handlers and app extensions. */
|
|
1930
|
-
export function createAgentServer<
|
|
1931
|
-
M extends UIMessage,
|
|
1932
|
-
D extends AIEventDefs<M> & EventDefs,
|
|
1933
|
-
T extends ToolSet = ToolSet,
|
|
1934
|
-
>(options: CreateAgentServerOptions<M, D, T>): A2Server<D> {
|
|
1935
|
-
const {
|
|
1936
|
-
agent: definition,
|
|
1937
|
-
model,
|
|
1938
|
-
tools,
|
|
1939
|
-
instructions,
|
|
1940
|
-
generation,
|
|
1941
|
-
maxSteps,
|
|
1942
|
-
messageMetadata,
|
|
1943
|
-
generate,
|
|
1944
|
-
compaction,
|
|
1945
|
-
progress,
|
|
1946
|
-
handlers,
|
|
1947
|
-
...serverOptions
|
|
1948
|
-
} = options
|
|
1949
|
-
const builtIns = createHandlers({
|
|
1950
|
-
agent: definition,
|
|
1951
|
-
model,
|
|
1952
|
-
...(tools === undefined ? {} : { tools }),
|
|
1953
|
-
...(instructions === undefined ? {} : { instructions }),
|
|
1954
|
-
...(generation === undefined ? {} : { generation }),
|
|
1955
|
-
...(maxSteps === undefined ? {} : { maxSteps }),
|
|
1956
|
-
...(messageMetadata === undefined ? {} : { messageMetadata }),
|
|
1957
|
-
...(generate === undefined ? {} : { generate }),
|
|
1958
|
-
...(compaction === undefined ? {} : { compaction }),
|
|
1959
|
-
...(progress === undefined ? {} : { progress }),
|
|
1960
|
-
})
|
|
1961
|
-
const server = createServer({
|
|
1962
|
-
...serverOptions,
|
|
1963
|
-
contract: definition.contract,
|
|
1964
|
-
handlers: { ...builtIns, ...handlers },
|
|
1965
|
-
})
|
|
1966
|
-
setServerFetchHooks(server, { validateIngress: validateAgentIngress })
|
|
1967
|
-
return server
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
export type { Instructions, LanguageModel, ToolSet }
|
|
26
|
+
} from './ai-agent.ts'
|