experimental-a2 0.12.0 → 0.14.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 +47 -0
- package/dist/{actor-DJi3RsNu.d.ts → actor-BfQSE0KC.d.ts} +4 -4
- package/dist/{actor-DJi3RsNu.d.ts.map → actor-BfQSE0KC.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-client.js +1 -1
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +2 -2
- package/dist/{actor-shared-DI7J5upy.js → actor-shared-B5tJfzt-.js} +2 -2
- package/dist/{actor-shared-DI7J5upy.js.map → actor-shared-B5tJfzt-.js.map} +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +3 -3
- package/dist/ai-Cai-lCbj.d.ts +580 -0
- package/dist/ai-Cai-lCbj.d.ts.map +1 -0
- package/dist/ai-control-CcD4hh3y.js +119 -0
- package/dist/ai-control-CcD4hh3y.js.map +1 -0
- package/dist/ai-server.d.ts +16 -8
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +1322 -475
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -334
- package/dist/ai.js +838 -85
- package/dist/ai.js.map +1 -1
- package/dist/{client-P_NNNRM-.d.ts → client-BAEABRZB.d.ts} +2 -2
- package/dist/{client-P_NNNRM-.d.ts.map → client-BAEABRZB.d.ts.map} +1 -1
- package/dist/{client-Bf6uSEAk.js → client-BYzHjkwU.js} +21 -6
- package/dist/client-BYzHjkwU.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/{contract-48bUMgcL.js → contract-CKRg_E4q.js} +3 -26
- package/dist/contract-CKRg_E4q.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/{reducer-DJKWm3cp.d.ts → reducer-BcS9VDKC.d.ts} +4 -1
- package/dist/{reducer-DJKWm3cp.d.ts.map → reducer-BcS9VDKC.d.ts.map} +1 -1
- package/dist/reducer-DEMjEY_O.js +29 -0
- package/dist/reducer-DEMjEY_O.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +1 -1
- package/dist/{server-DjZZa1wr.d.ts → server-Bp5Nd1pF.d.ts} +3 -3
- package/dist/{server-DjZZa1wr.d.ts.map → server-Bp5Nd1pF.d.ts.map} +1 -1
- package/dist/{server-BeNADlCI.js → server-CjJSGcF7.js} +4 -3
- package/dist/server-CjJSGcF7.js.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +1 -1
- package/dist/{store-DtDOWLSn.d.ts → store-D_yhNdPz.d.ts} +7 -2
- package/dist/{store-DtDOWLSn.d.ts.map → store-D_yhNdPz.d.ts.map} +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.js +19 -0
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +1 -1
- package/dist/{store-redis-notify-BUCyXOn0.js → store-redis-notify-D2EI6gwX.js} +27 -2
- package/dist/store-redis-notify-D2EI6gwX.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/docs/guides/06-ai-agents.mdx +361 -76
- package/docs/reference/01-api.mdx +159 -27
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +145 -33
- package/examples/playground/app/agent/[agentId]/agent-queue.tsx +289 -0
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +14 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +38 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +294 -0
- package/examples/playground/app/agent/compaction-settings.test.ts +144 -0
- package/examples/playground/app/agent/compaction-settings.ts +49 -0
- package/examples/playground/app/agent/compaction-timeline.test.ts +337 -0
- package/examples/playground/app/agent/compaction-timeline.ts +198 -0
- package/examples/playground/app/agent/model.ts +56 -1
- package/examples/playground/app/agent/server.ts +9 -2
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +3 -13
- package/examples/playground/app/chat/model.ts +2 -2
- package/examples/playground/app/chat/server.ts +24 -17
- package/examples/playground/app/globals.css +333 -0
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/ai-client-state.ts +185 -0
- package/src/ai-control-server.ts +829 -0
- package/src/ai-control-state.ts +152 -0
- package/src/ai-control.ts +139 -0
- package/src/ai-coordinator.ts +99 -32
- package/src/ai-model-metadata.ts +108 -0
- package/src/ai-progress-batches.ts +68 -0
- package/src/ai-projector.ts +76 -15
- package/src/ai-sdk-step.ts +5 -2
- package/src/ai-server.ts +920 -638
- package/src/ai.ts +650 -110
- package/src/client.ts +31 -9
- package/src/licenses/Apache-2.0.txt +55 -0
- package/src/parse-partial-json.ts +441 -0
- package/src/reducer.ts +6 -0
- package/src/server.ts +8 -4
- package/src/store-postgres.ts +27 -0
- package/src/store-redis-core.ts +53 -1
- package/src/store-redis-notify.ts +1 -0
- package/src/store.ts +6 -0
- package/dist/ai.d.ts.map +0 -1
- package/dist/client-Bf6uSEAk.js.map +0 -1
- package/dist/contract-48bUMgcL.js.map +0 -1
- package/dist/server-BeNADlCI.js.map +0 -1
- package/dist/store-redis-notify-BUCyXOn0.js.map +0 -1
package/src/ai-server.ts
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
// oxlint-disable no-await-in-loop -- stream chunks and durable appends are
|
|
9
9
|
// ordered; parallel consumption would corrupt progress sequence and state.
|
|
10
10
|
|
|
11
|
+
import { createControlRuntime, ControlCancelled } from './ai-control-server.ts'
|
|
12
|
+
import type { ControlCommit } from './ai-control.ts'
|
|
11
13
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
12
|
-
import { convertToModelMessages } from 'ai'
|
|
14
|
+
import { asSchema, convertToModelMessages } from 'ai'
|
|
13
15
|
import type {
|
|
14
16
|
FinishReason,
|
|
15
17
|
Instructions,
|
|
@@ -22,6 +24,7 @@ import type {
|
|
|
22
24
|
UIMessageChunk,
|
|
23
25
|
} from 'ai'
|
|
24
26
|
import { generateAISDKStep, type AISDKStepSettings } from './ai-sdk-step.ts'
|
|
27
|
+
import { gatewayModelId, readModelLimits } from './ai-model-metadata.ts'
|
|
25
28
|
import type {
|
|
26
29
|
AIEventDefs,
|
|
27
30
|
AIState,
|
|
@@ -33,17 +36,13 @@ import type {
|
|
|
33
36
|
GenerationCompletedPayload,
|
|
34
37
|
GenerationFailedPayload,
|
|
35
38
|
GenerationProgressPayload,
|
|
36
|
-
GenerationRequestedPayload,
|
|
37
39
|
GenerationStartedPayload,
|
|
38
40
|
MessageCompletedPayload,
|
|
39
|
-
MessageCreatedPayload,
|
|
40
|
-
MessageInterruptedPayload,
|
|
41
41
|
ToolCalledPayload,
|
|
42
42
|
ToolResultPayload,
|
|
43
43
|
} from './ai.ts'
|
|
44
44
|
import {
|
|
45
45
|
aiCoordinatorReducer,
|
|
46
|
-
continuationReady,
|
|
47
46
|
type AICoordinatorState,
|
|
48
47
|
} from './ai-coordinator.ts'
|
|
49
48
|
import type { AppendInput, ContractEvent, EventDefs } from './contract.ts'
|
|
@@ -66,24 +65,23 @@ import {
|
|
|
66
65
|
|
|
67
66
|
function validateAgentIngress(context: ServerIngressContext): void {
|
|
68
67
|
const rejected = context.events.find((event) => {
|
|
69
|
-
if (event.type !== 'ai.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
if (event.type !== 'ai.control.requested') return true
|
|
69
|
+
const command = event.payload as Record<string, unknown>
|
|
70
|
+
if (
|
|
71
|
+
command['action'] === 'request-input' ||
|
|
72
|
+
command['action'] === 'tool-result'
|
|
73
|
+
)
|
|
74
|
+
return true
|
|
75
|
+
if (
|
|
76
|
+
command['action'] === 'send' ||
|
|
77
|
+
command['action'] === 'edit' ||
|
|
78
|
+
command['action'] === 'steer'
|
|
79
|
+
) {
|
|
80
|
+
return (
|
|
81
|
+
(command['message'] as { role?: unknown } | undefined)?.role !== 'user'
|
|
75
82
|
)
|
|
76
83
|
}
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
typeof payload !== 'object' ||
|
|
80
|
-
payload === null ||
|
|
81
|
-
!('message' in payload) ||
|
|
82
|
-
typeof payload.message !== 'object' ||
|
|
83
|
-
payload.message === null ||
|
|
84
|
-
!('role' in payload.message) ||
|
|
85
|
-
payload.message.role !== 'user'
|
|
86
|
-
)
|
|
84
|
+
return false
|
|
87
85
|
})
|
|
88
86
|
if (rejected !== undefined) {
|
|
89
87
|
throw new A2Error(
|
|
@@ -116,8 +114,9 @@ export type AgentGenerateContext<
|
|
|
116
114
|
generationId: string
|
|
117
115
|
responseMessageId: string
|
|
118
116
|
messages: M[]
|
|
117
|
+
modelMessages: ModelMessage[]
|
|
119
118
|
state: AIState<M>
|
|
120
|
-
|
|
119
|
+
session: Pick<HandlerContext<D>['session'], 'state'>
|
|
121
120
|
signal: AbortSignal
|
|
122
121
|
model: LanguageModel
|
|
123
122
|
tools: T
|
|
@@ -150,7 +149,7 @@ export type AgentResolverContext<
|
|
|
150
149
|
> = {
|
|
151
150
|
event: ContractEvent<D, 'ai.generation.requested'>
|
|
152
151
|
state: AIState<M>
|
|
153
|
-
|
|
152
|
+
session: Pick<HandlerContext<D>['session'], 'state'>
|
|
154
153
|
signal: AbortSignal
|
|
155
154
|
}
|
|
156
155
|
|
|
@@ -161,13 +160,29 @@ export type CompactionPolicy<
|
|
|
161
160
|
D extends AIEventDefs<M> & EventDefs,
|
|
162
161
|
> = {
|
|
163
162
|
shouldCompact(
|
|
164
|
-
context: AgentResolverContext<M, D> & {
|
|
163
|
+
context: AgentResolverContext<M, D> & {
|
|
164
|
+
messages: M[]
|
|
165
|
+
modelMessages: ModelMessage[]
|
|
166
|
+
},
|
|
165
167
|
): boolean | Promise<boolean>
|
|
166
168
|
compact(
|
|
167
|
-
context: AgentResolverContext<M, D> & {
|
|
169
|
+
context: AgentResolverContext<M, D> & {
|
|
170
|
+
messages: M[]
|
|
171
|
+
modelMessages: ModelMessage[]
|
|
172
|
+
},
|
|
168
173
|
): M[] | Promise<M[]>
|
|
169
174
|
}
|
|
170
175
|
|
|
176
|
+
export type AutomaticCompaction = {
|
|
177
|
+
thresholdTokens?: number
|
|
178
|
+
instructions?: string
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type CompactionOptions<
|
|
182
|
+
M extends UIMessage,
|
|
183
|
+
D extends AIEventDefs<M> & EventDefs,
|
|
184
|
+
> = false | AutomaticCompaction | CompactionPolicy<M, D>
|
|
185
|
+
|
|
171
186
|
export type AgentGenerationSettings<T extends ToolSet> = AISDKStepSettings<T>
|
|
172
187
|
|
|
173
188
|
export type CreateHandlersOptions<
|
|
@@ -196,7 +211,7 @@ export type CreateHandlersOptions<
|
|
|
196
211
|
* The custom stream owns its metadata chunks.
|
|
197
212
|
*/
|
|
198
213
|
generate?: AgentGenerate<M, D, T>
|
|
199
|
-
compaction?:
|
|
214
|
+
compaction?: Resolvable<CompactionOptions<M, D>, AgentResolverContext<M, D>>
|
|
200
215
|
/** Progress is durably flushed at either limit, whichever is reached first. */
|
|
201
216
|
progress?: { maxChunks?: number; maxDelayMs?: number }
|
|
202
217
|
}
|
|
@@ -210,13 +225,35 @@ export type CreateAgentServerOptions<
|
|
|
210
225
|
handlers?: ServerOptions<D>['handlers']
|
|
211
226
|
}
|
|
212
227
|
|
|
213
|
-
const
|
|
228
|
+
const checkAbort = (signal: AbortSignal): boolean => {
|
|
229
|
+
if (!signal.aborted) return false
|
|
230
|
+
const reason: unknown = signal.reason
|
|
231
|
+
if (
|
|
232
|
+
reason instanceof A2Error &&
|
|
233
|
+
(reason.code === 'CLAIM_EXPIRED' || reason.code === 'SUPERSEDED_ATTEMPT')
|
|
234
|
+
) {
|
|
235
|
+
throw reason
|
|
236
|
+
}
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const resolve = async <T, C extends { signal: AbortSignal }>(
|
|
214
241
|
value: Resolvable<T, C>,
|
|
215
242
|
context: C,
|
|
216
|
-
): Promise<T> =>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
243
|
+
): Promise<T> => {
|
|
244
|
+
let result: T
|
|
245
|
+
try {
|
|
246
|
+
result =
|
|
247
|
+
typeof value === 'function'
|
|
248
|
+
? await (value as (context: C) => T | Promise<T>)(context)
|
|
249
|
+
: value
|
|
250
|
+
} catch (error) {
|
|
251
|
+
checkAbort(context.signal)
|
|
252
|
+
throw error
|
|
253
|
+
}
|
|
254
|
+
checkAbort(context.signal)
|
|
255
|
+
return result
|
|
256
|
+
}
|
|
220
257
|
|
|
221
258
|
const modelName = (model: LanguageModel): string => {
|
|
222
259
|
if (typeof model === 'string') return model
|
|
@@ -265,10 +302,12 @@ const isBoundaryChunk = (chunk: UIMessageChunk | undefined): boolean =>
|
|
|
265
302
|
chunk.type === 'abort' ||
|
|
266
303
|
chunk.type === 'error')
|
|
267
304
|
|
|
305
|
+
type StreamRead<T> = Awaited<ReturnType<ReadableStreamDefaultReader<T>['read']>>
|
|
306
|
+
|
|
268
307
|
const nextOrTimer = async <T>(
|
|
269
|
-
next: Promise<
|
|
308
|
+
next: Promise<StreamRead<T>>,
|
|
270
309
|
delayMs: number,
|
|
271
|
-
): Promise<{ type: 'next'; result:
|
|
310
|
+
): Promise<{ type: 'next'; result: StreamRead<T> } | { type: 'timer' }> => {
|
|
272
311
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
273
312
|
try {
|
|
274
313
|
return await Promise.race([
|
|
@@ -305,6 +344,7 @@ async function generateWithAISDK<
|
|
|
305
344
|
model: context.model,
|
|
306
345
|
tools: context.tools,
|
|
307
346
|
messages: context.messages,
|
|
347
|
+
modelMessages: context.modelMessages,
|
|
308
348
|
responseMessageId: context.responseMessageId,
|
|
309
349
|
abortSignal: context.signal,
|
|
310
350
|
...(context.instructions === undefined
|
|
@@ -333,11 +373,11 @@ async function* consumeGeneration(options: {
|
|
|
333
373
|
const maxChunks = options.progress?.maxChunks ?? 16
|
|
334
374
|
const maxDelayMs = options.progress?.maxDelayMs ?? 30
|
|
335
375
|
let lastFlush = Date.now()
|
|
336
|
-
const
|
|
337
|
-
let pendingChunk =
|
|
376
|
+
const reader = options.source.stream.getReader()
|
|
377
|
+
let pendingChunk = reader.read()
|
|
338
378
|
try {
|
|
339
379
|
for (;;) {
|
|
340
|
-
let result:
|
|
380
|
+
let result: StreamRead<UIMessageChunk>
|
|
341
381
|
if (pending.length > 0) {
|
|
342
382
|
const remaining = Math.max(0, maxDelayMs - (Date.now() - lastFlush))
|
|
343
383
|
const outcome = await nextOrTimer(pendingChunk, remaining)
|
|
@@ -355,7 +395,7 @@ async function* consumeGeneration(options: {
|
|
|
355
395
|
pending.push(chunk)
|
|
356
396
|
if (chunk.type === 'finish') streamedFinishReason = chunk.finishReason
|
|
357
397
|
if (chunk.type === 'error') streamedError = chunk.errorText
|
|
358
|
-
pendingChunk =
|
|
398
|
+
pendingChunk = reader.read()
|
|
359
399
|
|
|
360
400
|
if (pending.length >= maxChunks || isBoundaryChunk(pending.at(-1))) {
|
|
361
401
|
yield { type: 'progress', chunks: pending.splice(0) }
|
|
@@ -366,6 +406,15 @@ async function* consumeGeneration(options: {
|
|
|
366
406
|
if (pending.length > 0)
|
|
367
407
|
yield { type: 'progress', chunks: pending.splice(0) }
|
|
368
408
|
throw error
|
|
409
|
+
} finally {
|
|
410
|
+
void pendingChunk.catch(() => {})
|
|
411
|
+
try {
|
|
412
|
+
await reader.cancel()
|
|
413
|
+
} catch {
|
|
414
|
+
// Cleanup must not replace the error that ended generation.
|
|
415
|
+
} finally {
|
|
416
|
+
reader.releaseLock()
|
|
417
|
+
}
|
|
369
418
|
}
|
|
370
419
|
|
|
371
420
|
let completion: GenerationCompletion | undefined
|
|
@@ -389,46 +438,249 @@ async function* consumeGeneration(options: {
|
|
|
389
438
|
}
|
|
390
439
|
}
|
|
391
440
|
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
441
|
+
const foldAIEvents = <
|
|
442
|
+
M extends UIMessage,
|
|
443
|
+
D extends AIEventDefs<M> & EventDefs,
|
|
444
|
+
>(options: {
|
|
445
|
+
agent: AgentDefinition<M, D>
|
|
446
|
+
state: AIState<M>
|
|
447
|
+
events: ContractEvent<D>[]
|
|
448
|
+
}): AIState<M> => {
|
|
449
|
+
let state = options.state
|
|
450
|
+
for (const event of options.events)
|
|
451
|
+
state = options.agent.reducer.fold(state, event)
|
|
398
452
|
return state
|
|
399
453
|
}
|
|
400
454
|
|
|
401
|
-
const
|
|
455
|
+
const summarize = async <
|
|
456
|
+
M extends UIMessage,
|
|
457
|
+
D extends AIEventDefs<M> & EventDefs,
|
|
458
|
+
T extends ToolSet,
|
|
459
|
+
>(options: {
|
|
460
|
+
context: AgentGenerateContext<M, D, T>
|
|
461
|
+
instructions: string | undefined
|
|
462
|
+
}): Promise<{ summary: string; usage?: LanguageModelUsage }> => {
|
|
463
|
+
const instruction = [
|
|
464
|
+
'Summarize this conversation so you can continue the task after the earlier context is replaced by your summary.',
|
|
465
|
+
'Preserve the objective, constraints, decisions, exact identifiers, completed actions and their results, unresolved questions, and next steps. Distinguish observations from assumptions.',
|
|
466
|
+
'Respond only with the summary as text. Do not call any tools or continue working on the task.',
|
|
467
|
+
options.instructions,
|
|
468
|
+
]
|
|
469
|
+
.filter(Boolean)
|
|
470
|
+
.join('\n')
|
|
471
|
+
const generation = { ...options.context.generation }
|
|
472
|
+
for (const key of Object.keys(generation)) {
|
|
473
|
+
if (
|
|
474
|
+
key.startsWith('on') ||
|
|
475
|
+
key.startsWith('experimental_on') ||
|
|
476
|
+
[
|
|
477
|
+
'repairToolCall',
|
|
478
|
+
'experimental_repairToolCall',
|
|
479
|
+
'experimental_refineToolInput',
|
|
480
|
+
'experimental_transform',
|
|
481
|
+
'toolApproval',
|
|
482
|
+
].includes(key)
|
|
483
|
+
) {
|
|
484
|
+
Reflect.deleteProperty(generation, key)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const tools = Object.fromEntries(
|
|
488
|
+
Object.entries(options.context.tools).map(([name, tool]) => {
|
|
489
|
+
const definition = { ...tool, needsApproval: false }
|
|
490
|
+
for (const key of [
|
|
491
|
+
'execute',
|
|
492
|
+
'onInputStart',
|
|
493
|
+
'onInputDelta',
|
|
494
|
+
'onInputAvailable',
|
|
495
|
+
]) {
|
|
496
|
+
Reflect.deleteProperty(definition, key)
|
|
497
|
+
}
|
|
498
|
+
return [name, definition]
|
|
499
|
+
}),
|
|
500
|
+
) as T
|
|
501
|
+
const source = await generateWithAISDK(
|
|
502
|
+
{
|
|
503
|
+
...options.context,
|
|
504
|
+
generation,
|
|
505
|
+
tools,
|
|
506
|
+
modelMessages: [
|
|
507
|
+
...options.context.modelMessages,
|
|
508
|
+
{ role: 'user', content: instruction },
|
|
509
|
+
],
|
|
510
|
+
responseMessageId: `${options.context.generationId}:summary`,
|
|
511
|
+
},
|
|
512
|
+
undefined,
|
|
513
|
+
)
|
|
514
|
+
let summary = ''
|
|
515
|
+
let calledTool = false
|
|
516
|
+
let finish: AgentGenerationFinish | undefined
|
|
517
|
+
for await (const update of consumeGeneration({ source })) {
|
|
518
|
+
options.context.signal.throwIfAborted()
|
|
519
|
+
if (update.type === 'finish') {
|
|
520
|
+
finish = update
|
|
521
|
+
continue
|
|
522
|
+
}
|
|
523
|
+
for (const chunk of update.chunks) {
|
|
524
|
+
if (chunk.type === 'text-delta') summary += chunk.delta
|
|
525
|
+
if (chunk.type.startsWith('tool-')) calledTool = true
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (
|
|
529
|
+
calledTool ||
|
|
530
|
+
summary.trim().length === 0 ||
|
|
531
|
+
finish?.finishReason !== 'stop'
|
|
532
|
+
) {
|
|
533
|
+
throw new Error(
|
|
534
|
+
'compaction must produce a complete text summary without calling tools',
|
|
535
|
+
)
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
summary,
|
|
539
|
+
...(finish.usage === undefined ? {} : { usage: finish.usage }),
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const activeCompaction = <M extends UIMessage>(
|
|
544
|
+
state: AIState<M>,
|
|
545
|
+
): AIState<M>['compaction'] => {
|
|
402
546
|
const compaction = state.compaction
|
|
403
|
-
|
|
404
|
-
|
|
547
|
+
return compaction?.status === 'completed' &&
|
|
548
|
+
compaction.messages !== undefined &&
|
|
549
|
+
state.messages.some((message) => message.id === compaction.throughMessageId)
|
|
550
|
+
? compaction
|
|
551
|
+
: null
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const contextMessages = async <
|
|
555
|
+
M extends UIMessage,
|
|
556
|
+
D extends AIEventDefs<M> & EventDefs,
|
|
557
|
+
>(options: {
|
|
558
|
+
agent: AgentDefinition<M, D>
|
|
559
|
+
session: HandlerContext<D>['session']
|
|
560
|
+
snapshot: { state: AIState<M>; index: number }
|
|
561
|
+
coordinator: AICoordinatorState
|
|
562
|
+
appended?: ContractEvent<D>[]
|
|
563
|
+
excluded?: ReadonlySet<string>
|
|
564
|
+
}): Promise<M[]> => {
|
|
565
|
+
const { agent, session, snapshot } = options
|
|
566
|
+
const appended = options.appended ?? []
|
|
567
|
+
const state = foldAIEvents({ agent, events: appended, state: snapshot.state })
|
|
568
|
+
const compaction = activeCompaction(state)
|
|
569
|
+
const queued = new Set(
|
|
570
|
+
options.coordinator.queued.map((item) => item.messageId),
|
|
571
|
+
)
|
|
572
|
+
const visible = (messages: M[]): M[] =>
|
|
573
|
+
messages.filter((message) => !queued.has(message.id))
|
|
574
|
+
if (compaction === null) return visible(state.messages)
|
|
575
|
+
const retained = new Set(compaction.retainedMessageIds ?? [])
|
|
576
|
+
const throughIndex = compaction.throughIndex
|
|
577
|
+
if (throughIndex !== undefined) {
|
|
578
|
+
const [prefix, priorCoordinator, tail] = await Promise.all([
|
|
579
|
+
throughIndex === snapshot.index
|
|
580
|
+
? Promise.resolve(snapshot)
|
|
581
|
+
: session.state(agent.reducer, { through: throughIndex }),
|
|
582
|
+
session.state(aiCoordinatorReducer(agent.contract), {
|
|
583
|
+
through: throughIndex,
|
|
584
|
+
}),
|
|
585
|
+
throughIndex >= snapshot.index
|
|
586
|
+
? Promise.resolve([])
|
|
587
|
+
: session.history({ gte: throughIndex + 1, lte: snapshot.index }),
|
|
588
|
+
])
|
|
589
|
+
for (const item of priorCoordinator.state.queued)
|
|
590
|
+
retained.add(item.messageId)
|
|
591
|
+
const events =
|
|
592
|
+
options.excluded === undefined
|
|
593
|
+
? tail
|
|
594
|
+
: withoutGenerationLifecycle(tail, options.excluded)
|
|
595
|
+
const context = foldAIEvents({
|
|
596
|
+
agent,
|
|
597
|
+
events: [...events, ...appended],
|
|
598
|
+
state: {
|
|
599
|
+
...prefix.state,
|
|
600
|
+
messages: [
|
|
601
|
+
...compaction.messages!,
|
|
602
|
+
...prefix.state.messages.filter((message) =>
|
|
603
|
+
retained.has(message.id),
|
|
604
|
+
),
|
|
605
|
+
],
|
|
606
|
+
activeProjection: null,
|
|
607
|
+
},
|
|
608
|
+
})
|
|
609
|
+
const positions = new Map<string, number>()
|
|
610
|
+
for (const message of [...compaction.messages!, ...state.messages]) {
|
|
611
|
+
if (!positions.has(message.id)) positions.set(message.id, positions.size)
|
|
612
|
+
}
|
|
613
|
+
return visible(
|
|
614
|
+
context.messages.toSorted(
|
|
615
|
+
(left, right) =>
|
|
616
|
+
(positions.get(left.id) ?? positions.size) -
|
|
617
|
+
(positions.get(right.id) ?? positions.size),
|
|
618
|
+
),
|
|
619
|
+
)
|
|
405
620
|
}
|
|
406
621
|
const boundary = state.messages.findIndex(
|
|
407
622
|
(message) => message.id === compaction.throughMessageId,
|
|
408
623
|
)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
.filter((message) => retained.has(message.id)),
|
|
417
|
-
...state.messages.slice(boundary + 1),
|
|
418
|
-
]
|
|
624
|
+
return visible([
|
|
625
|
+
...compaction.messages!,
|
|
626
|
+
...state.messages
|
|
627
|
+
.slice(0, boundary + 1)
|
|
628
|
+
.filter((message) => retained.has(message.id)),
|
|
629
|
+
...state.messages.slice(boundary + 1),
|
|
630
|
+
])
|
|
419
631
|
}
|
|
420
632
|
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
633
|
+
const modelContext = async <M extends UIMessage>(options: {
|
|
634
|
+
messages: M[]
|
|
635
|
+
state: AIState<M>
|
|
636
|
+
tools: ToolSet
|
|
637
|
+
}): Promise<ModelMessage[]> => {
|
|
638
|
+
const messages = await convertToModelMessages(options.messages, {
|
|
639
|
+
tools: options.tools,
|
|
640
|
+
})
|
|
641
|
+
const summary = activeCompaction(options.state)?.summary
|
|
642
|
+
return summary === undefined
|
|
643
|
+
? messages
|
|
644
|
+
: [{ role: 'user', content: summary }, ...messages]
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const estimateInputTokens = async (options: {
|
|
648
|
+
messages: ModelMessage[]
|
|
649
|
+
instructions: Instructions | undefined
|
|
650
|
+
tools: ToolSet
|
|
651
|
+
}): Promise<number> => {
|
|
652
|
+
const tools = await Promise.all(
|
|
653
|
+
Object.entries(options.tools).map(async ([name, tool]) => ({
|
|
654
|
+
name,
|
|
655
|
+
description: tool.description,
|
|
656
|
+
inputSchema: await asSchema(tool.inputSchema).jsonSchema,
|
|
657
|
+
})),
|
|
658
|
+
)
|
|
659
|
+
return Math.ceil(
|
|
660
|
+
new TextEncoder().encode(
|
|
661
|
+
JSON.stringify({
|
|
662
|
+
messages: options.messages,
|
|
663
|
+
instructions: options.instructions,
|
|
664
|
+
tools,
|
|
665
|
+
}),
|
|
666
|
+
).byteLength / 4,
|
|
667
|
+
)
|
|
427
668
|
}
|
|
428
669
|
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
670
|
+
const measuredInputTokens = (options: {
|
|
671
|
+
estimate: number
|
|
672
|
+
model: string
|
|
673
|
+
calibration: AICoordinatorState['calibration']
|
|
674
|
+
}): number => {
|
|
675
|
+
const previous = options.calibration
|
|
676
|
+
return previous !== undefined &&
|
|
677
|
+
previous.model === options.model &&
|
|
678
|
+
options.estimate >= previous.estimate
|
|
679
|
+
? Math.max(
|
|
680
|
+
options.estimate,
|
|
681
|
+
Math.ceil(previous.inputTokens + options.estimate - previous.estimate),
|
|
682
|
+
)
|
|
683
|
+
: options.estimate
|
|
432
684
|
}
|
|
433
685
|
|
|
434
686
|
type ToolCallClassification = 'automatic' | 'approval' | 'unknown'
|
|
@@ -675,36 +927,6 @@ const lifecycleEvents = <D extends EventDefs, T extends ToolSet>(options: {
|
|
|
675
927
|
return { events: result, pending }
|
|
676
928
|
}
|
|
677
929
|
|
|
678
|
-
function queuedMessagesAt<D extends EventDefs>(
|
|
679
|
-
history: ContractEvent<D>[],
|
|
680
|
-
): AICoordinatorState['queued'] {
|
|
681
|
-
const queued: AICoordinatorState['queued'] = []
|
|
682
|
-
for (const event of history) {
|
|
683
|
-
if (event.type === 'ai.message.created') {
|
|
684
|
-
const payload = event.payload as MessageCreatedPayload<UIMessage>
|
|
685
|
-
if (payload.message.role !== 'user') continue
|
|
686
|
-
const duplicate = queued.findIndex(
|
|
687
|
-
(item) => item.messageId === payload.message.id,
|
|
688
|
-
)
|
|
689
|
-
if (duplicate !== -1) queued.splice(duplicate, 1)
|
|
690
|
-
queued.push({
|
|
691
|
-
index: event.index,
|
|
692
|
-
messageId: payload.message.id,
|
|
693
|
-
generate: payload.generate !== false,
|
|
694
|
-
})
|
|
695
|
-
continue
|
|
696
|
-
}
|
|
697
|
-
if (event.type !== 'ai.generation.requested') continue
|
|
698
|
-
const request = event.payload as GenerationRequestedPayload
|
|
699
|
-
if (request.reason !== 'message') continue
|
|
700
|
-
const requested = queued.findIndex(
|
|
701
|
-
(item) => item.messageId === request.messageId,
|
|
702
|
-
)
|
|
703
|
-
if (requested !== -1) queued.splice(0, requested + 1)
|
|
704
|
-
}
|
|
705
|
-
return queued
|
|
706
|
-
}
|
|
707
|
-
|
|
708
930
|
function withoutGenerationLifecycle<D extends EventDefs>(
|
|
709
931
|
history: ContractEvent<D>[],
|
|
710
932
|
generationIds: ReadonlySet<string>,
|
|
@@ -772,6 +994,62 @@ function withoutGenerationLifecycle<D extends EventDefs>(
|
|
|
772
994
|
})
|
|
773
995
|
}
|
|
774
996
|
|
|
997
|
+
const validateCompaction = <
|
|
998
|
+
M extends UIMessage,
|
|
999
|
+
D extends AIEventDefs<M> & EventDefs,
|
|
1000
|
+
T extends ToolSet,
|
|
1001
|
+
>(options: {
|
|
1002
|
+
compaction: CompactionOptions<M, D>
|
|
1003
|
+
explicit: boolean
|
|
1004
|
+
generation: AgentGenerationSettings<T> | undefined
|
|
1005
|
+
tools: T | undefined
|
|
1006
|
+
}): CompactionOptions<M, D> => {
|
|
1007
|
+
let compaction = options.compaction
|
|
1008
|
+
if (
|
|
1009
|
+
compaction !== false &&
|
|
1010
|
+
(typeof compaction !== 'object' || compaction === null)
|
|
1011
|
+
) {
|
|
1012
|
+
throw new TypeError('compaction must resolve to false or a policy object')
|
|
1013
|
+
}
|
|
1014
|
+
if (compaction !== false && 'then' in compaction) {
|
|
1015
|
+
void Promise.resolve(compaction).catch(() => {})
|
|
1016
|
+
throw new TypeError('compaction options must be a policy or a resolver')
|
|
1017
|
+
}
|
|
1018
|
+
if (compaction !== false && !('shouldCompact' in compaction)) {
|
|
1019
|
+
if (
|
|
1020
|
+
compaction.thresholdTokens !== undefined &&
|
|
1021
|
+
(!Number.isSafeInteger(compaction.thresholdTokens) ||
|
|
1022
|
+
compaction.thresholdTokens < 1)
|
|
1023
|
+
) {
|
|
1024
|
+
throw new TypeError(
|
|
1025
|
+
'compaction.thresholdTokens must be a positive safe integer',
|
|
1026
|
+
)
|
|
1027
|
+
}
|
|
1028
|
+
const choice = options.generation?.toolChoice
|
|
1029
|
+
const unsupported =
|
|
1030
|
+
options.generation?.output !== undefined
|
|
1031
|
+
? 'structured output'
|
|
1032
|
+
: choice !== undefined && choice !== 'auto' && choice !== 'none'
|
|
1033
|
+
? 'forced tool choice'
|
|
1034
|
+
: Object.values(options.tools ?? {}).some(
|
|
1035
|
+
(tool) =>
|
|
1036
|
+
tool.type === 'provider' && tool.isProviderExecuted === true,
|
|
1037
|
+
)
|
|
1038
|
+
? 'provider-executed tools'
|
|
1039
|
+
: undefined
|
|
1040
|
+
if (unsupported !== undefined) {
|
|
1041
|
+
if (options.explicit) {
|
|
1042
|
+
throw new TypeError(
|
|
1043
|
+
`automatic compaction does not support ${unsupported}; use a custom policy or compaction: false`,
|
|
1044
|
+
)
|
|
1045
|
+
}
|
|
1046
|
+
compaction = false
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
return compaction
|
|
1051
|
+
}
|
|
1052
|
+
|
|
775
1053
|
/**
|
|
776
1054
|
* Build the ordinary A2 handler table for the built-in agent protocol.
|
|
777
1055
|
* Application handlers can be spread beside this table.
|
|
@@ -808,92 +1086,25 @@ export function createHandlers<
|
|
|
808
1086
|
throw new TypeError('maxSteps must be a positive integer or Infinity')
|
|
809
1087
|
}
|
|
810
1088
|
|
|
1089
|
+
const configuredCompaction =
|
|
1090
|
+
options.compaction ?? (options.generate === undefined ? {} : false)
|
|
1091
|
+
const staticCompaction =
|
|
1092
|
+
typeof configuredCompaction === 'function'
|
|
1093
|
+
? undefined
|
|
1094
|
+
: validateCompaction({
|
|
1095
|
+
compaction: configuredCompaction,
|
|
1096
|
+
explicit: options.compaction !== undefined,
|
|
1097
|
+
generation: options.generation,
|
|
1098
|
+
tools: options.tools,
|
|
1099
|
+
})
|
|
1100
|
+
|
|
811
1101
|
const tools = options.tools ?? ({} as T)
|
|
812
|
-
const generation = options.generation ?? {}
|
|
1102
|
+
const generation: AgentGenerationSettings<T> = options.generation ?? {}
|
|
813
1103
|
const maxSteps = options.maxSteps ?? Number.POSITIVE_INFINITY
|
|
814
|
-
const
|
|
1104
|
+
const control = createControlRuntime({ agent: options.agent })
|
|
1105
|
+
const coordinator = control.coordinator
|
|
815
1106
|
const promptCache = new Map<string, Promise<ModelMessage[]>>()
|
|
816
1107
|
|
|
817
|
-
const coordinatorStateAt = (
|
|
818
|
-
history: ContractEvent<D>[],
|
|
819
|
-
frontier = Number.POSITIVE_INFINITY,
|
|
820
|
-
): AICoordinatorState => {
|
|
821
|
-
let state = coordinator.initialState
|
|
822
|
-
for (const event of history) {
|
|
823
|
-
if (event.index >= frontier) break
|
|
824
|
-
state = coordinator.fold(state, event)
|
|
825
|
-
}
|
|
826
|
-
return state
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
const requestForNextMessage = (
|
|
830
|
-
state: AICoordinatorState,
|
|
831
|
-
): AppendInput<D> | undefined => {
|
|
832
|
-
if (state.closed || state.response !== undefined) return undefined
|
|
833
|
-
const next = state.queued.find((item) => item.generate !== false)
|
|
834
|
-
if (next === undefined) return undefined
|
|
835
|
-
return {
|
|
836
|
-
type: 'ai.generation.requested',
|
|
837
|
-
id: `ai.generate:message:${next.messageId}`,
|
|
838
|
-
payload: { messageId: next.messageId, reason: 'message' },
|
|
839
|
-
} as AppendInput<D>
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
const scheduleNext = async (
|
|
843
|
-
ctx: Pick<HandlerContext<D>, 'session'>,
|
|
844
|
-
): Promise<AppendInput<D> | void> =>
|
|
845
|
-
requestForNextMessage(
|
|
846
|
-
(await ctx.session.state(coordinator, { through: 'latest' })).state,
|
|
847
|
-
)
|
|
848
|
-
|
|
849
|
-
const continueIfReady = async (
|
|
850
|
-
ctx: Pick<HandlerContext<D>, 'session'>,
|
|
851
|
-
generationId: string,
|
|
852
|
-
): Promise<void> => {
|
|
853
|
-
const state = (await ctx.session.state(coordinator, { through: 'latest' }))
|
|
854
|
-
.state
|
|
855
|
-
const response = state.response
|
|
856
|
-
if (response?.generation?.generationId !== generationId) {
|
|
857
|
-
return
|
|
858
|
-
}
|
|
859
|
-
const input = response.inputResponse
|
|
860
|
-
const inputReady =
|
|
861
|
-
response.completion?.generationId === generationId &&
|
|
862
|
-
response.failure === undefined &&
|
|
863
|
-
input !== undefined &&
|
|
864
|
-
response.inputs.length === 0 &&
|
|
865
|
-
response.calls.every(
|
|
866
|
-
(call) =>
|
|
867
|
-
call.terminal ||
|
|
868
|
-
(call.call.providerExecuted === true &&
|
|
869
|
-
call.call.supportsDeferredResults !== true &&
|
|
870
|
-
call.approval !== undefined &&
|
|
871
|
-
call.response !== undefined),
|
|
872
|
-
)
|
|
873
|
-
if (inputReady) {
|
|
874
|
-
await ctx.session.append('continue-after-input', {
|
|
875
|
-
type: 'ai.generation.requested',
|
|
876
|
-
id: `ai.generate:input:${encodeURIComponent(response.responseMessageId)}:${encodeURIComponent(input.generationId)}:${encodeURIComponent(input.inputId)}`,
|
|
877
|
-
payload: {
|
|
878
|
-
messageId: response.responseMessageId,
|
|
879
|
-
responseMessageId: response.responseMessageId,
|
|
880
|
-
reason: 'input',
|
|
881
|
-
},
|
|
882
|
-
} as AppendInput<D>)
|
|
883
|
-
return
|
|
884
|
-
}
|
|
885
|
-
if (!continuationReady(state)) return
|
|
886
|
-
await ctx.session.append('continue-after-tools', {
|
|
887
|
-
type: 'ai.generation.requested',
|
|
888
|
-
id: `ai.generate:tools:${generationId}`,
|
|
889
|
-
payload: {
|
|
890
|
-
messageId: response.responseMessageId,
|
|
891
|
-
responseMessageId: response.responseMessageId,
|
|
892
|
-
reason: 'tool',
|
|
893
|
-
},
|
|
894
|
-
} as AppendInput<D>)
|
|
895
|
-
}
|
|
896
|
-
|
|
897
1108
|
type RuntimeTool = {
|
|
898
1109
|
execute?: (
|
|
899
1110
|
input: unknown,
|
|
@@ -905,9 +1116,7 @@ export function createHandlers<
|
|
|
905
1116
|
) => unknown
|
|
906
1117
|
}
|
|
907
1118
|
|
|
908
|
-
type ToolHandlerContext =
|
|
909
|
-
| HandlerContext<D, 'ai.tool.called'>
|
|
910
|
-
| HandlerContext<D, 'ai.approval.responded'>
|
|
1119
|
+
type ToolHandlerContext = HandlerContext<D, 'ai.tool.execution.requested'>
|
|
911
1120
|
|
|
912
1121
|
const resultEvent = (
|
|
913
1122
|
call: ToolCalledPayload,
|
|
@@ -946,49 +1155,48 @@ export function createHandlers<
|
|
|
946
1155
|
},
|
|
947
1156
|
}) as AppendInput<D>
|
|
948
1157
|
|
|
949
|
-
const promptMessages = (
|
|
950
|
-
|
|
951
|
-
call: ToolCalledPayload
|
|
952
|
-
|
|
953
|
-
): Promise<ModelMessage[]> => {
|
|
954
|
-
const
|
|
1158
|
+
const promptMessages = (input: {
|
|
1159
|
+
ctx: ToolHandlerContext
|
|
1160
|
+
call: ToolCalledPayload
|
|
1161
|
+
generation: GenerationStartedPayload
|
|
1162
|
+
}): Promise<ModelMessage[]> => {
|
|
1163
|
+
const { ctx, call, generation: owner } = input
|
|
1164
|
+
const key = promptCacheKey(ctx.event.sessionId, call.generationId)
|
|
955
1165
|
const cached = promptCache.get(key)
|
|
956
1166
|
if (cached) return cached
|
|
957
1167
|
const computation = (async () => {
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
1168
|
+
const frontier = owner.promptThroughIndex
|
|
1169
|
+
if (frontier === undefined)
|
|
1170
|
+
throw new TypeError('AI work requires a prompt checkpoint')
|
|
1171
|
+
const [snapshot, atPrompt, tail] = await Promise.all([
|
|
1172
|
+
ctx.session.state(options.agent.reducer, { through: frontier }),
|
|
1173
|
+
ctx.session.state(coordinator, { through: frontier }),
|
|
1174
|
+
ctx.session.history({ gte: frontier + 1, lte: ctx.event.index }),
|
|
1175
|
+
])
|
|
1176
|
+
const appended = tail.filter(
|
|
961
1177
|
(event) =>
|
|
962
|
-
event.type === 'ai.
|
|
963
|
-
|
|
1178
|
+
(event.type === 'ai.generation.started' ||
|
|
1179
|
+
event.type === 'ai.compaction.requested' ||
|
|
1180
|
+
event.type === 'ai.compaction.completed') &&
|
|
1181
|
+
(event.payload as { generationId: string }).generationId ===
|
|
964
1182
|
call.generationId,
|
|
965
1183
|
)
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
)
|
|
983
|
-
const frontier = started?.index ?? Number.POSITIVE_INFINITY
|
|
984
|
-
const state = replay(
|
|
985
|
-
options.agent,
|
|
986
|
-
history.filter((event) => event.index < frontier),
|
|
987
|
-
)
|
|
988
|
-
return convertToModelMessages(
|
|
989
|
-
activeContextMessages(state, coordinatorState),
|
|
990
|
-
{ tools },
|
|
991
|
-
)
|
|
1184
|
+
const state = foldAIEvents({
|
|
1185
|
+
agent: options.agent,
|
|
1186
|
+
events: appended,
|
|
1187
|
+
state: snapshot.state,
|
|
1188
|
+
})
|
|
1189
|
+
return modelContext({
|
|
1190
|
+
messages: await contextMessages({
|
|
1191
|
+
agent: options.agent,
|
|
1192
|
+
session: ctx.session,
|
|
1193
|
+
snapshot,
|
|
1194
|
+
coordinator: atPrompt.state,
|
|
1195
|
+
appended,
|
|
1196
|
+
}),
|
|
1197
|
+
state,
|
|
1198
|
+
tools,
|
|
1199
|
+
})
|
|
992
1200
|
})()
|
|
993
1201
|
promptCache.set(key, computation)
|
|
994
1202
|
void computation.catch(() => {
|
|
@@ -1003,9 +1211,9 @@ export function createHandlers<
|
|
|
1003
1211
|
error: unknown,
|
|
1004
1212
|
): AppendInput<D> | void => {
|
|
1005
1213
|
const schedulerFailure = consumeSchedulerSendFailure(error)
|
|
1006
|
-
if (ctx.signal
|
|
1214
|
+
if (checkAbort(ctx.signal)) return
|
|
1007
1215
|
if (schedulerFailure === 'retryable') throw error
|
|
1008
|
-
return resultEvent(call,
|
|
1216
|
+
return resultEvent(call, `${ctx.event.id}:execution:error`, {
|
|
1009
1217
|
error: errorMessage(error),
|
|
1010
1218
|
})
|
|
1011
1219
|
}
|
|
@@ -1033,69 +1241,73 @@ export function createHandlers<
|
|
|
1033
1241
|
return toolExecutionFailure(ctx, call, error)
|
|
1034
1242
|
}
|
|
1035
1243
|
if (iterator === undefined) {
|
|
1036
|
-
return resultEvent(call,
|
|
1244
|
+
return resultEvent(call, `${ctx.event.id}:execution:0`, { output })
|
|
1037
1245
|
}
|
|
1038
1246
|
let last: unknown
|
|
1039
1247
|
let sequence = 0
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
result
|
|
1044
|
-
} catch (error) {
|
|
1045
|
-
return toolExecutionFailure(ctx, call, error)
|
|
1046
|
-
}
|
|
1047
|
-
if (result.done) break
|
|
1048
|
-
if (ctx.signal.aborted) {
|
|
1248
|
+
let done = false
|
|
1249
|
+
try {
|
|
1250
|
+
for (;;) {
|
|
1251
|
+
let result: IteratorResult<unknown>
|
|
1049
1252
|
try {
|
|
1050
|
-
await iterator.
|
|
1051
|
-
} catch {
|
|
1052
|
-
|
|
1253
|
+
result = await iterator.next()
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
return toolExecutionFailure(ctx, call, error)
|
|
1053
1256
|
}
|
|
1054
|
-
|
|
1257
|
+
if (result.done) {
|
|
1258
|
+
if (ctx.signal.reason instanceof A2Error) throw ctx.signal.reason
|
|
1259
|
+
done = true
|
|
1260
|
+
break
|
|
1261
|
+
}
|
|
1262
|
+
if (checkAbort(ctx.signal)) return
|
|
1263
|
+
last = result.value
|
|
1264
|
+
await control.append({
|
|
1265
|
+
ctx,
|
|
1266
|
+
name: `tool:${call.toolCallId}:preliminary:${sequence}`,
|
|
1267
|
+
events: [
|
|
1268
|
+
resultEvent(
|
|
1269
|
+
call,
|
|
1270
|
+
`${ctx.event.id}:execution:${ctx.attempt}:${sequence}:preliminary`,
|
|
1271
|
+
{
|
|
1272
|
+
output: result.value,
|
|
1273
|
+
preliminary: true,
|
|
1274
|
+
},
|
|
1275
|
+
),
|
|
1276
|
+
],
|
|
1277
|
+
})
|
|
1278
|
+
sequence += 1
|
|
1055
1279
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
await ctx.session.append(
|
|
1059
|
-
`tool:${call.toolCallId}:preliminary:${sequence}`,
|
|
1060
|
-
resultEvent(call, `execution:${sequence}:preliminary`, {
|
|
1061
|
-
output: result.value,
|
|
1062
|
-
preliminary: true,
|
|
1063
|
-
}),
|
|
1064
|
-
)
|
|
1065
|
-
} catch (error) {
|
|
1280
|
+
} finally {
|
|
1281
|
+
if (!done) {
|
|
1066
1282
|
try {
|
|
1067
1283
|
await iterator.return?.()
|
|
1068
1284
|
} catch {
|
|
1069
|
-
// Preserve the
|
|
1285
|
+
// Preserve the failure or cancellation that ended execution.
|
|
1070
1286
|
}
|
|
1071
|
-
throw error
|
|
1072
1287
|
}
|
|
1073
|
-
sequence += 1
|
|
1074
1288
|
}
|
|
1075
1289
|
return resultEvent(
|
|
1076
1290
|
call,
|
|
1077
|
-
|
|
1291
|
+
`${ctx.event.id}:execution:${sequence}:final`,
|
|
1078
1292
|
sequence === 0 ? {} : { output: last },
|
|
1079
1293
|
)
|
|
1080
1294
|
}
|
|
1081
1295
|
|
|
1082
|
-
const executeTool = async (
|
|
1083
|
-
ctx: ToolHandlerContext
|
|
1084
|
-
call: ToolCalledPayload
|
|
1085
|
-
|
|
1296
|
+
const executeTool = async (input: {
|
|
1297
|
+
ctx: ToolHandlerContext
|
|
1298
|
+
call: ToolCalledPayload
|
|
1299
|
+
generation: GenerationStartedPayload
|
|
1300
|
+
}): Promise<AppendInput<D> | void> => {
|
|
1301
|
+
const { ctx, call } = input
|
|
1086
1302
|
const tool = tools[call.toolName] as RuntimeTool | undefined
|
|
1087
1303
|
const execute = tool?.execute
|
|
1088
1304
|
if (execute === undefined) {
|
|
1089
|
-
return resultEvent(call,
|
|
1305
|
+
return resultEvent(call, `${ctx.event.id}:execution:error`, {
|
|
1090
1306
|
error: `Tool '${call.toolName}' has no server executor`,
|
|
1091
1307
|
})
|
|
1092
1308
|
}
|
|
1093
|
-
const messages = await promptMessages(
|
|
1094
|
-
|
|
1095
|
-
call,
|
|
1096
|
-
ctx.session.history,
|
|
1097
|
-
)
|
|
1098
|
-
if (ctx.signal.aborted) return
|
|
1309
|
+
const messages = await promptMessages(input)
|
|
1310
|
+
if (checkAbort(ctx.signal)) return
|
|
1099
1311
|
const scope: AmbientToolScope = {
|
|
1100
1312
|
contract: options.agent.contract,
|
|
1101
1313
|
context: ctx,
|
|
@@ -1107,100 +1319,77 @@ export function createHandlers<
|
|
|
1107
1319
|
)
|
|
1108
1320
|
}
|
|
1109
1321
|
|
|
1110
|
-
const
|
|
1111
|
-
ctx:
|
|
1112
|
-
): Promise<AppendInput<D
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
candidate.approval?.approvalId === ctx.event.payload.approvalId &&
|
|
1144
|
-
candidate.approval.messageId === ctx.event.payload.messageId &&
|
|
1145
|
-
candidate.approval.generationId === ctx.event.payload.generationId &&
|
|
1146
|
-
candidate.responseIndex === ctx.event.index,
|
|
1147
|
-
)
|
|
1148
|
-
if (
|
|
1149
|
-
current === undefined ||
|
|
1150
|
-
response?.generation?.generationId !== current.call.generationId ||
|
|
1151
|
-
response.failure !== undefined ||
|
|
1152
|
-
current.terminal
|
|
1153
|
-
) {
|
|
1154
|
-
return
|
|
1155
|
-
}
|
|
1156
|
-
if (current.call.providerExecuted === true) {
|
|
1157
|
-
await continueIfReady(ctx, current.call.generationId)
|
|
1158
|
-
return
|
|
1159
|
-
}
|
|
1160
|
-
if (!ctx.event.payload.approved) {
|
|
1161
|
-
return resultEvent(current.call, 'execution:denied', { denied: true })
|
|
1322
|
+
const handleToolExecution = async (
|
|
1323
|
+
ctx: ToolHandlerContext,
|
|
1324
|
+
): Promise<AppendInput<D>> => {
|
|
1325
|
+
try {
|
|
1326
|
+
const state = (
|
|
1327
|
+
await ctx.session.state(control.reducer, { through: 'latest' })
|
|
1328
|
+
).state
|
|
1329
|
+
const request = ctx.event.payload
|
|
1330
|
+
const call = state.coordinator.response?.calls.find(
|
|
1331
|
+
(candidate) => candidate.work?.id === ctx.event.id,
|
|
1332
|
+
)
|
|
1333
|
+
if (
|
|
1334
|
+
state.active?.turnId !== request.turnId ||
|
|
1335
|
+
state.active.suspended ||
|
|
1336
|
+
state.active.version !== request.version ||
|
|
1337
|
+
state.coordinator.response?.failure !== undefined ||
|
|
1338
|
+
!call ||
|
|
1339
|
+
call.terminal ||
|
|
1340
|
+
call.work?.settled
|
|
1341
|
+
)
|
|
1342
|
+
return control.settled({ ctx, events: undefined })
|
|
1343
|
+
return control.settled({
|
|
1344
|
+
ctx,
|
|
1345
|
+
events: await executeTool({
|
|
1346
|
+
ctx,
|
|
1347
|
+
call: request.call,
|
|
1348
|
+
generation: request.generation,
|
|
1349
|
+
}),
|
|
1350
|
+
})
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
if (control.cancelled({ error, signal: ctx.signal }))
|
|
1353
|
+
return control.settled({ ctx, events: undefined })
|
|
1354
|
+
throw error
|
|
1162
1355
|
}
|
|
1163
|
-
return executeTool(ctx, current.call)
|
|
1164
1356
|
}
|
|
1165
1357
|
|
|
1166
1358
|
const generationHandler = async (
|
|
1167
1359
|
ctx: HandlerContext<D, 'ai.generation.requested'>,
|
|
1168
1360
|
): Promise<void | AppendInput<D> | readonly AppendInput<D>[]> => {
|
|
1169
|
-
|
|
1361
|
+
if (checkAbort(ctx.signal)) return
|
|
1170
1362
|
const requestId = ctx.event.id
|
|
1171
1363
|
const request = ctx.event.payload
|
|
1364
|
+
const snapshot = await ctx.session.state(options.agent.reducer, {
|
|
1365
|
+
through: 'latest',
|
|
1366
|
+
})
|
|
1367
|
+
if (
|
|
1368
|
+
snapshot.state.active?.phase === 'paused' ||
|
|
1369
|
+
snapshot.state.active?.phase === 'pausing'
|
|
1370
|
+
)
|
|
1371
|
+
return
|
|
1172
1372
|
const coordinatorState = (
|
|
1173
|
-
await ctx.session.state(coordinator, { through:
|
|
1373
|
+
await ctx.session.state(coordinator, { through: snapshot.index })
|
|
1174
1374
|
).state
|
|
1175
1375
|
if (
|
|
1176
1376
|
coordinatorState.closed ||
|
|
1177
1377
|
coordinatorState.response?.activeRequestId !== requestId
|
|
1178
|
-
) {
|
|
1179
|
-
return
|
|
1180
|
-
}
|
|
1181
|
-
const terminal = history.some((event) => {
|
|
1182
|
-
if (event.type === 'ai.generation.completed') {
|
|
1183
|
-
return (
|
|
1184
|
-
(event.payload as GenerationCompletedPayload).requestId === requestId
|
|
1185
|
-
)
|
|
1186
|
-
}
|
|
1187
|
-
if (event.type !== 'ai.generation.failed') return false
|
|
1188
|
-
const payload = event.payload as GenerationFailedPayload
|
|
1189
|
-
return payload.requestId === requestId && payload.superseded !== true
|
|
1190
|
-
})
|
|
1191
|
-
if (terminal) return
|
|
1192
|
-
|
|
1193
|
-
const previousStarts = history.filter(
|
|
1194
|
-
(event) =>
|
|
1195
|
-
event.type === 'ai.generation.started' &&
|
|
1196
|
-
(event.payload as GenerationStartedPayload).requestId === requestId,
|
|
1197
1378
|
)
|
|
1198
|
-
|
|
1199
|
-
|
|
1379
|
+
return
|
|
1380
|
+
const response = coordinatorState.response
|
|
1381
|
+
const current =
|
|
1382
|
+
response.generation?.requestId === requestId
|
|
1383
|
+
? response.generation
|
|
1384
|
+
: undefined
|
|
1385
|
+
if (
|
|
1386
|
+
current !== undefined &&
|
|
1387
|
+
(response.completion !== undefined ||
|
|
1388
|
+
(response.failure !== undefined &&
|
|
1389
|
+
response.failure.superseded !== true))
|
|
1200
1390
|
)
|
|
1201
|
-
if (priorAttempts.some((priorAttempt) => priorAttempt >= ctx.attempt)) {
|
|
1202
1391
|
return
|
|
1203
|
-
|
|
1392
|
+
if (current !== undefined && current.attempt >= ctx.attempt) return
|
|
1204
1393
|
const attempt = ctx.attempt
|
|
1205
1394
|
const generationId = `${requestId}:generation:${attempt}`
|
|
1206
1395
|
const responseMessageId =
|
|
@@ -1208,202 +1397,363 @@ export function createHandlers<
|
|
|
1208
1397
|
(request.reason === 'message'
|
|
1209
1398
|
? `${request.messageId}:assistant`
|
|
1210
1399
|
: request.messageId)
|
|
1211
|
-
|
|
1212
|
-
const responseStepCount = history.filter(
|
|
1213
|
-
(event) =>
|
|
1214
|
-
event.type === 'ai.generation.completed' &&
|
|
1215
|
-
(event.payload as GenerationCompletedPayload).responseMessageId ===
|
|
1216
|
-
responseMessageId,
|
|
1217
|
-
).length
|
|
1218
|
-
|
|
1219
|
-
const sourceCoordinatorState = coordinatorStateAt(history, ctx.event.index)
|
|
1220
|
-
if (request.reason === 'tool') {
|
|
1221
|
-
const prefix = 'ai.generate:tools:'
|
|
1222
|
-
if (!requestId.startsWith(prefix)) return
|
|
1223
|
-
const sourceGenerationId = requestId.slice(prefix.length)
|
|
1224
|
-
if (
|
|
1225
|
-
!continuationReady(sourceCoordinatorState) ||
|
|
1226
|
-
sourceCoordinatorState.response?.generation?.generationId !==
|
|
1227
|
-
sourceGenerationId ||
|
|
1228
|
-
sourceCoordinatorState.response.responseMessageId !==
|
|
1229
|
-
request.messageId ||
|
|
1230
|
-
sourceCoordinatorState.response.responseMessageId !==
|
|
1231
|
-
request.responseMessageId
|
|
1232
|
-
) {
|
|
1233
|
-
return
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const previous = previousStarts
|
|
1238
|
-
.filter(
|
|
1239
|
-
(event) =>
|
|
1240
|
-
(event.payload as GenerationStartedPayload).attempt < attempt,
|
|
1241
|
-
)
|
|
1242
|
-
.toSorted(
|
|
1243
|
-
(left, right) =>
|
|
1244
|
-
(right.payload as GenerationStartedPayload).attempt -
|
|
1245
|
-
(left.payload as GenerationStartedPayload).attempt,
|
|
1246
|
-
)[0]
|
|
1247
|
-
|
|
1248
|
-
const incompleteId = previous
|
|
1249
|
-
? (previous.payload as GenerationStartedPayload).generationId
|
|
1250
|
-
: undefined
|
|
1251
|
-
const replacedGenerationIds = new Set<string>()
|
|
1252
|
-
if (incompleteId !== undefined) replacedGenerationIds.add(incompleteId)
|
|
1400
|
+
const responseStepCount = response.stepCount
|
|
1253
1401
|
if (
|
|
1254
|
-
request.reason === '
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1402
|
+
request.reason === 'tool' &&
|
|
1403
|
+
(requestId !==
|
|
1404
|
+
`ai.generate:tools:${response.source?.generation.generationId}` ||
|
|
1405
|
+
response.responseMessageId !== request.messageId ||
|
|
1406
|
+
response.responseMessageId !== request.responseMessageId)
|
|
1407
|
+
)
|
|
1408
|
+
return
|
|
1409
|
+
const previous = current
|
|
1410
|
+
const replaced =
|
|
1411
|
+
previous === undefined
|
|
1412
|
+
? []
|
|
1413
|
+
: [{ generation: previous, frontier: response.promptThroughIndex! }]
|
|
1414
|
+
if (request.reason === 'retry' && response.source?.failed) {
|
|
1415
|
+
replaced.push({
|
|
1416
|
+
generation: response.source.generation,
|
|
1417
|
+
frontier: response.source.promptThroughIndex,
|
|
1418
|
+
})
|
|
1260
1419
|
}
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
replacedGenerationIds,
|
|
1420
|
+
const replacedGenerationIds = new Set(
|
|
1421
|
+
replaced.map(({ generation: owner }) => owner.generationId),
|
|
1264
1422
|
)
|
|
1265
|
-
let state =
|
|
1423
|
+
let state = snapshot.state
|
|
1424
|
+
if (replaced.length > 0) {
|
|
1425
|
+
const through = Math.min(...replaced.map(({ frontier }) => frontier))
|
|
1426
|
+
const baseline = await ctx.session.state(options.agent.reducer, {
|
|
1427
|
+
through,
|
|
1428
|
+
})
|
|
1429
|
+
const tail = await ctx.session.history({
|
|
1430
|
+
gte: through + 1,
|
|
1431
|
+
lte: snapshot.index,
|
|
1432
|
+
})
|
|
1433
|
+
state = foldAIEvents({
|
|
1434
|
+
agent: options.agent,
|
|
1435
|
+
events: withoutGenerationLifecycle(tail, replacedGenerationIds),
|
|
1436
|
+
state: baseline.state,
|
|
1437
|
+
})
|
|
1438
|
+
}
|
|
1266
1439
|
const resolverContext: AgentResolverContext<M, D> = {
|
|
1267
1440
|
event: ctx.event,
|
|
1268
1441
|
state,
|
|
1269
|
-
|
|
1442
|
+
session: {
|
|
1443
|
+
state: (reducer, readOptions) =>
|
|
1444
|
+
ctx.session.state(reducer, {
|
|
1445
|
+
...readOptions,
|
|
1446
|
+
through: readOptions?.through ?? snapshot.index,
|
|
1447
|
+
}),
|
|
1448
|
+
},
|
|
1270
1449
|
signal: ctx.signal,
|
|
1271
1450
|
}
|
|
1272
1451
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1452
|
+
let generationStarted = false
|
|
1453
|
+
try {
|
|
1454
|
+
const resolvedModel = await resolve(options.model, resolverContext)
|
|
1455
|
+
if (resolvedModel === undefined) {
|
|
1456
|
+
throw new TypeError('the model resolver returned undefined')
|
|
1457
|
+
}
|
|
1458
|
+
if (checkAbort(ctx.signal)) return
|
|
1459
|
+
const resolvedInstructions =
|
|
1460
|
+
options.instructions === undefined
|
|
1461
|
+
? undefined
|
|
1462
|
+
: await resolve(options.instructions, resolverContext)
|
|
1463
|
+
if (checkAbort(ctx.signal)) return
|
|
1464
|
+
const compaction =
|
|
1465
|
+
typeof configuredCompaction === 'function'
|
|
1466
|
+
? validateCompaction({
|
|
1467
|
+
compaction: await resolve(configuredCompaction, resolverContext),
|
|
1468
|
+
explicit: true,
|
|
1469
|
+
generation: options.generation,
|
|
1470
|
+
tools: options.tools,
|
|
1471
|
+
})
|
|
1472
|
+
: staticCompaction!
|
|
1473
|
+
if (checkAbort(ctx.signal)) return
|
|
1474
|
+
|
|
1475
|
+
if (request.reason === 'tool' && responseStepCount >= maxSteps) {
|
|
1476
|
+
const source = response.source?.generation
|
|
1477
|
+
if (source === undefined) return
|
|
1478
|
+
return {
|
|
1479
|
+
type: 'ai.generation.failed',
|
|
1480
|
+
id: `${requestId}:step-limit`,
|
|
1481
|
+
payload: {
|
|
1482
|
+
requestId: source.requestId,
|
|
1483
|
+
messageId: source.messageId,
|
|
1484
|
+
generationId: source.generationId,
|
|
1485
|
+
responseMessageId,
|
|
1486
|
+
error: `agent exceeded the ${maxSteps}-step limit`,
|
|
1487
|
+
stepLimit: true,
|
|
1488
|
+
},
|
|
1489
|
+
} as AppendInput<D>
|
|
1490
|
+
}
|
|
1295
1491
|
|
|
1296
|
-
|
|
1297
|
-
requestId,
|
|
1298
|
-
messageId: request.messageId,
|
|
1299
|
-
generationId,
|
|
1300
|
-
responseMessageId,
|
|
1301
|
-
attempt,
|
|
1302
|
-
model: modelName(resolvedModel),
|
|
1303
|
-
}
|
|
1304
|
-
const startEvents: AppendInput<D>[] = []
|
|
1305
|
-
if (previous) {
|
|
1306
|
-
const payload = previous.payload as GenerationStartedPayload
|
|
1307
|
-
const superseded: GenerationFailedPayload = {
|
|
1492
|
+
const started: GenerationStartedPayload = {
|
|
1308
1493
|
requestId,
|
|
1309
|
-
messageId:
|
|
1310
|
-
generationId
|
|
1311
|
-
responseMessageId
|
|
1312
|
-
|
|
1313
|
-
|
|
1494
|
+
messageId: request.messageId,
|
|
1495
|
+
generationId,
|
|
1496
|
+
responseMessageId,
|
|
1497
|
+
attempt,
|
|
1498
|
+
model: modelName(resolvedModel),
|
|
1499
|
+
promptThroughIndex: snapshot.index,
|
|
1500
|
+
}
|
|
1501
|
+
const catalogModelId = gatewayModelId(resolvedModel)
|
|
1502
|
+
const gatewayOptions = options.generation?.providerOptions?.['gateway']
|
|
1503
|
+
const usesFallbackModels =
|
|
1504
|
+
Array.isArray(gatewayOptions?.['models']) &&
|
|
1505
|
+
gatewayOptions['models'].length > 0
|
|
1506
|
+
const discoversMetadata =
|
|
1507
|
+
compaction !== false &&
|
|
1508
|
+
!('shouldCompact' in compaction) &&
|
|
1509
|
+
compaction.thresholdTokens === undefined &&
|
|
1510
|
+
!usesFallbackModels &&
|
|
1511
|
+
catalogModelId !== undefined
|
|
1512
|
+
const startEvents: AppendInput<D>[] = []
|
|
1513
|
+
if (
|
|
1514
|
+
discoversMetadata &&
|
|
1515
|
+
!Object.hasOwn(state.modelMetadata, catalogModelId)
|
|
1516
|
+
) {
|
|
1517
|
+
startEvents.push({
|
|
1518
|
+
type: 'ai.model.metadata.requested',
|
|
1519
|
+
id: `ai.model.metadata:${encodeURIComponent(options.agent.contract.name)}:${encodeURIComponent(ctx.event.sessionId)}:${encodeURIComponent(catalogModelId)}`,
|
|
1520
|
+
payload: { modelId: catalogModelId },
|
|
1521
|
+
} as AppendInput<D>)
|
|
1522
|
+
}
|
|
1523
|
+
if (previous) {
|
|
1524
|
+
const payload = previous
|
|
1525
|
+
const superseded: GenerationFailedPayload = {
|
|
1526
|
+
requestId,
|
|
1527
|
+
messageId: payload.messageId,
|
|
1528
|
+
generationId: payload.generationId,
|
|
1529
|
+
responseMessageId: payload.responseMessageId,
|
|
1530
|
+
error: 'generation attempt was superseded after an incomplete run',
|
|
1531
|
+
superseded: true,
|
|
1532
|
+
}
|
|
1533
|
+
startEvents.push({
|
|
1534
|
+
type: 'ai.generation.failed',
|
|
1535
|
+
id: `${payload.generationId}:superseded`,
|
|
1536
|
+
payload: superseded,
|
|
1537
|
+
} as AppendInput<D>)
|
|
1314
1538
|
}
|
|
1315
1539
|
startEvents.push({
|
|
1316
|
-
type: 'ai.generation.
|
|
1317
|
-
id:
|
|
1318
|
-
payload:
|
|
1540
|
+
type: 'ai.generation.started',
|
|
1541
|
+
id: generationId,
|
|
1542
|
+
payload: started,
|
|
1319
1543
|
} as AppendInput<D>)
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
await ctx.session.append('generation-start', ...startEvents)
|
|
1327
|
-
|
|
1328
|
-
const startedCoordinatorState = (
|
|
1329
|
-
await ctx.session.state(coordinator, { through: 'latest' })
|
|
1330
|
-
).state
|
|
1331
|
-
if (
|
|
1332
|
-
startedCoordinatorState.response?.activeRequestId !== requestId ||
|
|
1333
|
-
startedCoordinatorState.response.generation?.generationId !== generationId
|
|
1334
|
-
) {
|
|
1335
|
-
return
|
|
1336
|
-
}
|
|
1544
|
+
const generationEvents = await control.append({
|
|
1545
|
+
ctx,
|
|
1546
|
+
name: 'generation-start',
|
|
1547
|
+
events: startEvents,
|
|
1548
|
+
})
|
|
1549
|
+
generationStarted = true
|
|
1337
1550
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1551
|
+
if (checkAbort(ctx.signal)) return
|
|
1552
|
+
|
|
1553
|
+
const promptCoordinatorState = coordinatorState
|
|
1554
|
+
const messages = await contextMessages({
|
|
1555
|
+
agent: options.agent,
|
|
1556
|
+
session: ctx.session,
|
|
1557
|
+
snapshot: { state, index: snapshot.index },
|
|
1558
|
+
coordinator: promptCoordinatorState,
|
|
1559
|
+
excluded: replacedGenerationIds,
|
|
1560
|
+
})
|
|
1561
|
+
let generationMessages = messages
|
|
1562
|
+
let modelMessages = await modelContext({ messages, state, tools })
|
|
1563
|
+
const baseContext: AgentGenerateContext<M, D, T> = {
|
|
1564
|
+
request: ctx.event,
|
|
1565
|
+
requestId,
|
|
1566
|
+
generationId,
|
|
1567
|
+
responseMessageId,
|
|
1568
|
+
messages,
|
|
1569
|
+
modelMessages,
|
|
1570
|
+
state,
|
|
1571
|
+
session: resolverContext.session,
|
|
1572
|
+
signal: ctx.signal,
|
|
1573
|
+
model: resolvedModel,
|
|
1574
|
+
tools,
|
|
1575
|
+
...(resolvedInstructions === undefined
|
|
1576
|
+
? {}
|
|
1577
|
+
: { instructions: resolvedInstructions }),
|
|
1578
|
+
generation,
|
|
1579
|
+
}
|
|
1580
|
+
const policy = compaction
|
|
1581
|
+
const tracksInput =
|
|
1582
|
+
policy !== false &&
|
|
1583
|
+
!('shouldCompact' in policy) &&
|
|
1584
|
+
(policy.thresholdTokens !== undefined || discoversMetadata)
|
|
1585
|
+
const metadata =
|
|
1586
|
+
catalogModelId === undefined
|
|
1587
|
+
? undefined
|
|
1588
|
+
: state.modelMetadata[catalogModelId]
|
|
1589
|
+
const limits =
|
|
1590
|
+
metadata?.status === 'resolved' && !usesFallbackModels
|
|
1591
|
+
? metadata.limits
|
|
1592
|
+
: undefined
|
|
1593
|
+
let inputTokenEstimate: number | undefined
|
|
1594
|
+
let compacted = false
|
|
1595
|
+
const canCompact = response.source?.canCompact ?? true
|
|
1596
|
+
if (policy && canCompact) {
|
|
1597
|
+
const compactionContext = {
|
|
1598
|
+
...resolverContext,
|
|
1356
1599
|
messages,
|
|
1357
|
-
|
|
1358
|
-
? {}
|
|
1359
|
-
: {
|
|
1360
|
-
retainedMessageIds: promptCoordinatorState.queued.map(
|
|
1361
|
-
(item) => item.messageId,
|
|
1362
|
-
),
|
|
1363
|
-
}),
|
|
1600
|
+
modelMessages,
|
|
1364
1601
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1602
|
+
let shouldCompact: boolean
|
|
1603
|
+
if ('shouldCompact' in policy) {
|
|
1604
|
+
shouldCompact = await policy.shouldCompact(compactionContext)
|
|
1605
|
+
} else if (tracksInput) {
|
|
1606
|
+
inputTokenEstimate = await estimateInputTokens({
|
|
1607
|
+
messages: modelMessages,
|
|
1608
|
+
instructions: resolvedInstructions,
|
|
1609
|
+
tools,
|
|
1610
|
+
})
|
|
1611
|
+
const inputTokens = measuredInputTokens({
|
|
1612
|
+
estimate: inputTokenEstimate,
|
|
1613
|
+
model: modelName(resolvedModel),
|
|
1614
|
+
calibration: coordinatorState.calibration,
|
|
1615
|
+
})
|
|
1616
|
+
const threshold =
|
|
1617
|
+
policy.thresholdTokens ??
|
|
1618
|
+
(limits === undefined
|
|
1619
|
+
? undefined
|
|
1620
|
+
: Math.floor(
|
|
1621
|
+
Math.min(
|
|
1622
|
+
limits.contextWindow * 0.75,
|
|
1623
|
+
limits.contextWindow - (generation.maxOutputTokens ?? 0),
|
|
1624
|
+
),
|
|
1625
|
+
))
|
|
1626
|
+
if (
|
|
1627
|
+
limits !== undefined &&
|
|
1628
|
+
threshold !== undefined &&
|
|
1629
|
+
threshold <= 0
|
|
1630
|
+
) {
|
|
1631
|
+
throw new Error(
|
|
1632
|
+
`output allowance exhausts the ${limits.contextWindow}-token context window`,
|
|
1633
|
+
)
|
|
1634
|
+
}
|
|
1635
|
+
shouldCompact = threshold !== undefined && inputTokens >= threshold
|
|
1636
|
+
} else {
|
|
1637
|
+
shouldCompact = false
|
|
1638
|
+
}
|
|
1639
|
+
if (checkAbort(ctx.signal)) return
|
|
1640
|
+
if (shouldCompact) {
|
|
1641
|
+
const throughMessageId =
|
|
1642
|
+
state.messages.findLast(
|
|
1643
|
+
(message) =>
|
|
1644
|
+
!promptCoordinatorState.queued.some(
|
|
1645
|
+
(queued) => queued.messageId === message.id,
|
|
1646
|
+
),
|
|
1647
|
+
)?.id ?? request.messageId
|
|
1648
|
+
const throughIndex = snapshot.index
|
|
1649
|
+
generationEvents.push(
|
|
1650
|
+
...(await control.append({
|
|
1651
|
+
ctx,
|
|
1652
|
+
name: 'compaction-requested',
|
|
1653
|
+
events: [
|
|
1654
|
+
{
|
|
1655
|
+
type: 'ai.compaction.requested',
|
|
1656
|
+
id: `${generationId}:compaction:requested`,
|
|
1657
|
+
payload: { generationId, throughMessageId, throughIndex },
|
|
1658
|
+
} as AppendInput<D>,
|
|
1659
|
+
],
|
|
1660
|
+
})),
|
|
1661
|
+
)
|
|
1662
|
+
const result = !('shouldCompact' in policy)
|
|
1663
|
+
? {
|
|
1664
|
+
messages: [] as M[],
|
|
1665
|
+
...(await summarize({
|
|
1666
|
+
context: baseContext,
|
|
1667
|
+
instructions: policy.instructions,
|
|
1668
|
+
})),
|
|
1669
|
+
}
|
|
1670
|
+
: { messages: await policy.compact(compactionContext) }
|
|
1671
|
+
if (checkAbort(ctx.signal)) return
|
|
1672
|
+
const completed: CompactionCompletedPayload<M> = {
|
|
1673
|
+
generationId,
|
|
1674
|
+
throughMessageId,
|
|
1675
|
+
throughIndex,
|
|
1676
|
+
...result,
|
|
1677
|
+
...(promptCoordinatorState.queued.length === 0
|
|
1678
|
+
? {}
|
|
1679
|
+
: {
|
|
1680
|
+
retainedMessageIds: promptCoordinatorState.queued.map(
|
|
1681
|
+
(item) => item.messageId,
|
|
1682
|
+
),
|
|
1683
|
+
}),
|
|
1684
|
+
}
|
|
1685
|
+
generationEvents.push(
|
|
1686
|
+
...(await control.append({
|
|
1687
|
+
ctx,
|
|
1688
|
+
name: 'compaction-completed',
|
|
1689
|
+
events: [
|
|
1690
|
+
{
|
|
1691
|
+
type: 'ai.compaction.completed',
|
|
1692
|
+
id: `${generationId}:compaction:completed`,
|
|
1693
|
+
payload: completed,
|
|
1694
|
+
} as AppendInput<D>,
|
|
1695
|
+
],
|
|
1696
|
+
})),
|
|
1697
|
+
)
|
|
1698
|
+
const queued = new Set(
|
|
1699
|
+
promptCoordinatorState.queued.map((item) => item.messageId),
|
|
1700
|
+
)
|
|
1701
|
+
generationMessages = result.messages.filter(
|
|
1702
|
+
(message) => !queued.has(message.id),
|
|
1703
|
+
)
|
|
1704
|
+
compacted = true
|
|
1373
1705
|
}
|
|
1374
1706
|
}
|
|
1375
|
-
}
|
|
1376
1707
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1708
|
+
const currentState = foldAIEvents({
|
|
1709
|
+
agent: options.agent,
|
|
1710
|
+
events: generationEvents,
|
|
1711
|
+
state: snapshot.state,
|
|
1712
|
+
})
|
|
1713
|
+
if (request.reason === 'retry' || previous !== undefined) {
|
|
1714
|
+
generationMessages = await contextMessages({
|
|
1715
|
+
agent: options.agent,
|
|
1716
|
+
session: ctx.session,
|
|
1717
|
+
snapshot,
|
|
1718
|
+
coordinator: promptCoordinatorState,
|
|
1719
|
+
appended: generationEvents,
|
|
1720
|
+
})
|
|
1721
|
+
}
|
|
1722
|
+
if (
|
|
1723
|
+
(policy && 'shouldCompact' in policy) ||
|
|
1724
|
+
generationMessages !== messages ||
|
|
1725
|
+
activeCompaction(currentState)?.summary !==
|
|
1726
|
+
activeCompaction(state)?.summary
|
|
1727
|
+
) {
|
|
1728
|
+
modelMessages = await modelContext({
|
|
1729
|
+
messages: generationMessages,
|
|
1730
|
+
state: currentState,
|
|
1731
|
+
tools,
|
|
1732
|
+
})
|
|
1733
|
+
}
|
|
1734
|
+
promptCache.set(
|
|
1735
|
+
promptCacheKey(ctx.event.sessionId, generationId),
|
|
1736
|
+
Promise.resolve(modelMessages),
|
|
1737
|
+
)
|
|
1738
|
+
if (tracksInput && (inputTokenEstimate === undefined || compacted)) {
|
|
1739
|
+
inputTokenEstimate = await estimateInputTokens({
|
|
1740
|
+
messages: modelMessages,
|
|
1741
|
+
instructions: resolvedInstructions,
|
|
1742
|
+
tools,
|
|
1743
|
+
})
|
|
1744
|
+
}
|
|
1391
1745
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
const resolvedInstructions =
|
|
1395
|
-
options.instructions === undefined
|
|
1396
|
-
? undefined
|
|
1397
|
-
: await resolve(options.instructions, resolverContext)
|
|
1398
|
-
if (ctx.signal.aborted) return
|
|
1746
|
+
let pendingToolCalls: PendingToolCall[] = []
|
|
1747
|
+
if (checkAbort(ctx.signal)) return
|
|
1399
1748
|
const generateContext: AgentGenerateContext<M, D, T> = {
|
|
1400
1749
|
request: ctx.event,
|
|
1401
1750
|
requestId,
|
|
1402
1751
|
generationId,
|
|
1403
1752
|
responseMessageId,
|
|
1404
1753
|
messages: generationMessages,
|
|
1754
|
+
modelMessages,
|
|
1405
1755
|
state: currentState,
|
|
1406
|
-
|
|
1756
|
+
session: resolverContext.session,
|
|
1407
1757
|
signal: ctx.signal,
|
|
1408
1758
|
model: resolvedModel,
|
|
1409
1759
|
tools,
|
|
@@ -1452,15 +1802,18 @@ export function createHandlers<
|
|
|
1452
1802
|
custom,
|
|
1453
1803
|
})
|
|
1454
1804
|
pendingToolCalls = lifecycle.pending
|
|
1455
|
-
await
|
|
1456
|
-
|
|
1457
|
-
{
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1805
|
+
await control.append({
|
|
1806
|
+
ctx,
|
|
1807
|
+
name: `generation-progress:${sequence}`,
|
|
1808
|
+
events: [
|
|
1809
|
+
{
|
|
1810
|
+
type: 'ai.generation.progress',
|
|
1811
|
+
id: `${generationId}:progress:${sequence}`,
|
|
1812
|
+
payload: progress,
|
|
1813
|
+
} as AppendInput<D>,
|
|
1814
|
+
...lifecycle.events,
|
|
1815
|
+
],
|
|
1816
|
+
})
|
|
1464
1817
|
sequence += 1
|
|
1465
1818
|
}
|
|
1466
1819
|
|
|
@@ -1470,6 +1823,7 @@ export function createHandlers<
|
|
|
1470
1823
|
messageId: request.messageId,
|
|
1471
1824
|
generationId,
|
|
1472
1825
|
responseMessageId,
|
|
1826
|
+
...(inputTokenEstimate === undefined ? {} : { inputTokenEstimate }),
|
|
1473
1827
|
...(finish.finishReason === undefined
|
|
1474
1828
|
? {}
|
|
1475
1829
|
: { finishReason: finish.finishReason }),
|
|
@@ -1513,19 +1867,13 @@ export function createHandlers<
|
|
|
1513
1867
|
} as AppendInput<D>,
|
|
1514
1868
|
]
|
|
1515
1869
|
} catch (error) {
|
|
1516
|
-
if (error instanceof
|
|
1517
|
-
if (
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
generationId,
|
|
1521
|
-
reason: 'aborted',
|
|
1522
|
-
}
|
|
1523
|
-
return {
|
|
1524
|
-
type: 'ai.message.interrupted',
|
|
1525
|
-
id: `${generationId}:interrupted`,
|
|
1526
|
-
payload: interrupted,
|
|
1527
|
-
} as AppendInput<D>
|
|
1870
|
+
if (error instanceof ControlCancelled) throw error
|
|
1871
|
+
if (error instanceof A2Error) {
|
|
1872
|
+
checkAbort(ctx.signal)
|
|
1873
|
+
throw error
|
|
1528
1874
|
}
|
|
1875
|
+
if (checkAbort(ctx.signal)) return
|
|
1876
|
+
if (!generationStarted) throw error
|
|
1529
1877
|
const failed: GenerationFailedPayload = {
|
|
1530
1878
|
requestId,
|
|
1531
1879
|
messageId: request.messageId,
|
|
@@ -1541,59 +1889,6 @@ export function createHandlers<
|
|
|
1541
1889
|
}
|
|
1542
1890
|
}
|
|
1543
1891
|
|
|
1544
|
-
const handleMessageCreated = async (
|
|
1545
|
-
ctx: HandlerContext<D, 'ai.message.created'>,
|
|
1546
|
-
): Promise<AppendInput<D> | void> => {
|
|
1547
|
-
if (
|
|
1548
|
-
ctx.event.payload.message.role !== 'user' ||
|
|
1549
|
-
ctx.event.payload.generate === false
|
|
1550
|
-
) {
|
|
1551
|
-
return
|
|
1552
|
-
}
|
|
1553
|
-
return scheduleNext(ctx)
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
const handleRetry = async (
|
|
1557
|
-
ctx: HandlerContext<D, 'ai.retry.requested'>,
|
|
1558
|
-
): Promise<AppendInput<D> | void> => {
|
|
1559
|
-
const state = (await ctx.session.state(coordinator, { through: 'latest' }))
|
|
1560
|
-
.state
|
|
1561
|
-
const response = state.response
|
|
1562
|
-
if (
|
|
1563
|
-
response?.status !== 'failed' ||
|
|
1564
|
-
response.rootMessageId !== ctx.event.payload.messageId ||
|
|
1565
|
-
response.responseMessageId !== ctx.event.payload.responseMessageId
|
|
1566
|
-
) {
|
|
1567
|
-
return
|
|
1568
|
-
}
|
|
1569
|
-
return {
|
|
1570
|
-
type: 'ai.generation.requested',
|
|
1571
|
-
id: `ai.generate:retry:${ctx.event.payload.retryId}`,
|
|
1572
|
-
payload: {
|
|
1573
|
-
messageId: response.rootMessageId,
|
|
1574
|
-
responseMessageId: response.responseMessageId,
|
|
1575
|
-
reason: 'retry',
|
|
1576
|
-
},
|
|
1577
|
-
} as AppendInput<D>
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
const handleInputResponse = async (
|
|
1581
|
-
ctx: HandlerContext<D, 'ai.input.responded'>,
|
|
1582
|
-
): Promise<void> => {
|
|
1583
|
-
const state = (await ctx.session.state(coordinator, { through: 'latest' }))
|
|
1584
|
-
.state
|
|
1585
|
-
const response = state.response
|
|
1586
|
-
if (
|
|
1587
|
-
response?.responseMessageId !== ctx.event.payload.messageId ||
|
|
1588
|
-
response.inputResponse?.index !== ctx.event.index ||
|
|
1589
|
-
response.inputResponse.generationId !== ctx.event.payload.generationId ||
|
|
1590
|
-
response.inputResponse.inputId !== ctx.event.payload.inputId
|
|
1591
|
-
) {
|
|
1592
|
-
return
|
|
1593
|
-
}
|
|
1594
|
-
await continueIfReady(ctx, ctx.event.payload.generationId)
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
1892
|
const clearPromptCache = (sessionId: string): void => {
|
|
1598
1893
|
const prefix = `${sessionId}\u001f`
|
|
1599
1894
|
for (const key of promptCache.keys()) {
|
|
@@ -1601,32 +1896,38 @@ export function createHandlers<
|
|
|
1601
1896
|
}
|
|
1602
1897
|
}
|
|
1603
1898
|
|
|
1604
|
-
const handleResponseEnded = async (
|
|
1605
|
-
ctx: HandlerContext<D, 'ai.message.completed' | 'ai.message.interrupted'>,
|
|
1606
|
-
): Promise<AppendInput<D> | void> => {
|
|
1607
|
-
clearPromptCache(ctx.event.sessionId)
|
|
1608
|
-
return scheduleNext(ctx)
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
1899
|
return {
|
|
1612
|
-
'ai.
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1900
|
+
'ai.model.metadata.requested': {
|
|
1901
|
+
handler: async (ctx) => {
|
|
1902
|
+
const limits = await readModelLimits({
|
|
1903
|
+
modelId: ctx.event.payload.modelId,
|
|
1904
|
+
signal: ctx.signal,
|
|
1905
|
+
})
|
|
1906
|
+
ctx.signal.throwIfAborted()
|
|
1907
|
+
return {
|
|
1908
|
+
type: 'ai.model.metadata.resolved',
|
|
1909
|
+
id: `${ctx.event.id}:resolved`,
|
|
1910
|
+
payload: { modelId: ctx.event.payload.modelId, limits },
|
|
1911
|
+
} as AppendInput<D>
|
|
1912
|
+
},
|
|
1617
1913
|
},
|
|
1914
|
+
'ai.control.requested': { lane: 'a2.ai.control', handler: control.handler },
|
|
1915
|
+
'ai.work.reported': { lane: 'a2.ai.control', handler: control.handler },
|
|
1618
1916
|
'ai.message.completed': {
|
|
1619
|
-
|
|
1620
|
-
|
|
1917
|
+
handler: async (ctx) => {
|
|
1918
|
+
clearPromptCache(ctx.event.sessionId)
|
|
1919
|
+
},
|
|
1621
1920
|
},
|
|
1622
1921
|
'ai.message.interrupted': {
|
|
1623
|
-
|
|
1624
|
-
|
|
1922
|
+
handler: async (ctx) => {
|
|
1923
|
+
clearPromptCache(ctx.event.sessionId)
|
|
1924
|
+
},
|
|
1625
1925
|
},
|
|
1626
1926
|
'ai.session.closed': {
|
|
1627
|
-
|
|
1927
|
+
lane: 'a2.ai.control',
|
|
1928
|
+
handler: async (ctx) => {
|
|
1628
1929
|
clearPromptCache(ctx.event.sessionId)
|
|
1629
|
-
return
|
|
1930
|
+
return control.handler(ctx)
|
|
1630
1931
|
},
|
|
1631
1932
|
},
|
|
1632
1933
|
'ai.generation.failed': {
|
|
@@ -1638,60 +1939,41 @@ export function createHandlers<
|
|
|
1638
1939
|
},
|
|
1639
1940
|
},
|
|
1640
1941
|
'ai.generation.requested': {
|
|
1641
|
-
lane: 'a2.ai.
|
|
1942
|
+
lane: 'a2.ai.model',
|
|
1642
1943
|
abortOn: {
|
|
1643
|
-
'ai.
|
|
1644
|
-
const
|
|
1645
|
-
trigger.payload.responseMessageId ??
|
|
1646
|
-
(trigger.payload.reason === 'message'
|
|
1647
|
-
? `${trigger.payload.messageId}:assistant`
|
|
1648
|
-
: trigger.payload.messageId)
|
|
1944
|
+
'ai.control.committed': (event, trigger) => {
|
|
1945
|
+
const active = (event.payload as ControlCommit).view
|
|
1649
1946
|
return (
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
event.payload.generationId ===
|
|
1653
|
-
`${trigger.id}:generation:${context.attempt}`)
|
|
1947
|
+
active?.turnId !== trigger.payload.control?.turnId ||
|
|
1948
|
+
active?.version !== trigger.payload.control?.version
|
|
1654
1949
|
)
|
|
1655
1950
|
},
|
|
1656
1951
|
'ai.session.closed': true,
|
|
1657
1952
|
},
|
|
1658
|
-
handler:
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
'ai.generation.failed': (event, trigger) =>
|
|
1667
|
-
event.payload.generationId === trigger.payload.generationId,
|
|
1668
|
-
'ai.message.interrupted': (event, trigger) =>
|
|
1669
|
-
event.payload.messageId === trigger.payload.messageId &&
|
|
1670
|
-
(event.payload.requestId === trigger.payload.requestId ||
|
|
1671
|
-
event.payload.generationId === trigger.payload.generationId),
|
|
1672
|
-
'ai.session.closed': true,
|
|
1953
|
+
handler: async (ctx) => {
|
|
1954
|
+
try {
|
|
1955
|
+
return control.settled({ ctx, events: await generationHandler(ctx) })
|
|
1956
|
+
} catch (error) {
|
|
1957
|
+
if (control.cancelled({ error, signal: ctx.signal }))
|
|
1958
|
+
return control.settled({ ctx, events: undefined })
|
|
1959
|
+
throw error
|
|
1960
|
+
}
|
|
1673
1961
|
},
|
|
1674
|
-
handler: handleToolCall,
|
|
1675
1962
|
},
|
|
1676
|
-
'ai.
|
|
1963
|
+
'ai.tool.execution.requested': {
|
|
1677
1964
|
abortOn: {
|
|
1678
1965
|
'ai.generation.failed': (event, trigger) =>
|
|
1679
|
-
event.payload.
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1966
|
+
event.payload.generationId === trigger.payload.call.generationId,
|
|
1967
|
+
'ai.control.committed': (event, trigger) => {
|
|
1968
|
+
const active = (event.payload as ControlCommit).view
|
|
1969
|
+
return (
|
|
1970
|
+
active?.turnId !== trigger.payload.turnId ||
|
|
1971
|
+
active?.version !== trigger.payload.version
|
|
1972
|
+
)
|
|
1973
|
+
},
|
|
1686
1974
|
'ai.session.closed': true,
|
|
1687
1975
|
},
|
|
1688
|
-
handler:
|
|
1689
|
-
},
|
|
1690
|
-
'ai.tool.result': {
|
|
1691
|
-
handler: async (ctx) => {
|
|
1692
|
-
if (ctx.event.payload.preliminary === true) return
|
|
1693
|
-
await continueIfReady(ctx, ctx.event.payload.generationId)
|
|
1694
|
-
},
|
|
1976
|
+
handler: handleToolExecution,
|
|
1695
1977
|
},
|
|
1696
1978
|
} as NonNullable<ServerOptions<D>['handlers']>
|
|
1697
1979
|
}
|