experimental-a2 0.11.0 → 0.13.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 +19 -0
- package/dist/{actor-D_54lz_1.d.ts → actor-DJi3RsNu.d.ts} +2 -2
- package/dist/{actor-D_54lz_1.d.ts.map → actor-DJi3RsNu.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-react.d.ts +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +1 -1
- package/dist/ai-server.d.ts +12 -4
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +477 -143
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +33 -2
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +39 -7
- package/dist/ai.js.map +1 -1
- package/dist/scheduler-qstash.d.ts +1 -1
- package/dist/scheduler-qstash.js +1 -1
- package/dist/scheduler-vercel.d.ts +1 -1
- package/dist/scheduler-vercel.js +1 -1
- package/dist/{server-Dkz2a84E.js → server-BeNADlCI.js} +8 -5
- package/dist/server-BeNADlCI.js.map +1 -0
- package/dist/{server-DwPrMqHB.d.ts → server-DjZZa1wr.d.ts} +8 -2
- package/dist/{server-DwPrMqHB.d.ts.map → server-DjZZa1wr.d.ts.map} +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/docs/guides/06-ai-agents.mdx +100 -18
- package/docs/reference/01-api.mdx +105 -5
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +31 -16
- 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 +128 -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 +47 -1
- package/examples/playground/app/agent/server.ts +3 -2
- package/examples/playground/app/globals.css +154 -0
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/ai-model-metadata.ts +108 -0
- package/src/ai-sdk-step.ts +5 -1
- package/src/ai-server.ts +712 -203
- package/src/ai.ts +99 -4
- package/src/server.ts +14 -3
- package/dist/server-Dkz2a84E.js.map +0 -1
package/dist/ai-server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as A2Error } from "./errors-DCk6ch5n.js";
|
|
2
2
|
import { f as consumeSchedulerSendFailure, p as installAmbientToolScopeStorage } from "./internal-Dq2qYxou.js";
|
|
3
|
-
import {
|
|
3
|
+
import { i as setServerFetchHooks, t as createServer } from "./server-BeNADlCI.js";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
-
import { convertToModelMessages, stepCountIs, streamText, toUIMessageStream } from "ai";
|
|
5
|
+
import { asSchema, convertToModelMessages, stepCountIs, streamText, toUIMessageStream } from "ai";
|
|
6
6
|
//#region src/ai-sdk-step.ts
|
|
7
7
|
const CONTROLLED_SETTINGS = [
|
|
8
8
|
"_internal",
|
|
@@ -45,7 +45,7 @@ const errorMessage$1 = (error) => error instanceof Error ? error.message : Strin
|
|
|
45
45
|
async function generateAISDKStep(input) {
|
|
46
46
|
const settings = safeSettings(input.settings);
|
|
47
47
|
const tools = modelToolSet(input.tools);
|
|
48
|
-
const messages = await convertToModelMessages(input.messages, { tools });
|
|
48
|
+
const messages = input.modelMessages ?? await convertToModelMessages(input.messages, { tools });
|
|
49
49
|
const streamOptions = {
|
|
50
50
|
...settings,
|
|
51
51
|
model: input.model,
|
|
@@ -85,6 +85,66 @@ async function generateAISDKStep(input) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
//#endregion
|
|
88
|
+
//#region src/ai-model-metadata.ts
|
|
89
|
+
const catalogUrl = "https://ai-gateway.vercel.sh/v1/models";
|
|
90
|
+
const catalogLifetimeMs = 36e5;
|
|
91
|
+
const catalogTimeoutMs = 5e3;
|
|
92
|
+
let catalog;
|
|
93
|
+
const gatewayModelId = (model) => {
|
|
94
|
+
if (typeof model === "string") return globalThis.AI_SDK_DEFAULT_PROVIDER === void 0 ? model : void 0;
|
|
95
|
+
return model.provider === "gateway" ? model.modelId : void 0;
|
|
96
|
+
};
|
|
97
|
+
const readCatalog = () => {
|
|
98
|
+
if (catalog && catalog.expiresAt > Date.now()) return catalog.promise;
|
|
99
|
+
const entry = {
|
|
100
|
+
expiresAt: Number.POSITIVE_INFINITY,
|
|
101
|
+
promise: (async () => {
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timeout = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("model catalog request timed out")), catalogTimeoutMs);
|
|
104
|
+
timeout.unref?.();
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetch(catalogUrl, { signal: controller.signal });
|
|
107
|
+
if (!response.ok) throw new Error(`model catalog request failed: HTTP ${response.status}`);
|
|
108
|
+
const body = await response.json();
|
|
109
|
+
if (typeof body !== "object" || body === null || !("data" in body) || !Array.isArray(body.data)) throw new Error("model catalog response must contain a data array");
|
|
110
|
+
const models = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const row of body.data) {
|
|
112
|
+
if (typeof row !== "object" || row === null || !("type" in row) || !("id" in row) || !("context_window" in row) || !("max_tokens" in row) || row.type !== "language" || typeof row.id !== "string" || typeof row.context_window !== "number" || typeof row.max_tokens !== "number" || !Number.isSafeInteger(row.context_window) || row.context_window <= 0 || !Number.isSafeInteger(row.max_tokens) || row.max_tokens <= 0) continue;
|
|
113
|
+
models.set(row.id, {
|
|
114
|
+
contextWindow: row.context_window,
|
|
115
|
+
maxOutputTokens: row.max_tokens
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return models;
|
|
119
|
+
} finally {
|
|
120
|
+
clearTimeout(timeout);
|
|
121
|
+
}
|
|
122
|
+
})()
|
|
123
|
+
};
|
|
124
|
+
catalog = entry;
|
|
125
|
+
entry.promise = entry.promise.then((models) => {
|
|
126
|
+
entry.expiresAt = Date.now() + catalogLifetimeMs;
|
|
127
|
+
return models;
|
|
128
|
+
}, (error) => {
|
|
129
|
+
if (catalog === entry) catalog = void 0;
|
|
130
|
+
throw error;
|
|
131
|
+
});
|
|
132
|
+
return entry.promise;
|
|
133
|
+
};
|
|
134
|
+
const readModelLimits = async (options) => {
|
|
135
|
+
options.signal.throwIfAborted();
|
|
136
|
+
let onAbort;
|
|
137
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
138
|
+
onAbort = () => reject(options.signal.reason);
|
|
139
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
140
|
+
});
|
|
141
|
+
try {
|
|
142
|
+
return (await Promise.race([readCatalog(), aborted])).get(options.modelId) ?? null;
|
|
143
|
+
} finally {
|
|
144
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
//#endregion
|
|
88
148
|
//#region src/ai-coordinator.ts
|
|
89
149
|
const updateResponse = (state, update) => state.response === void 0 ? state : {
|
|
90
150
|
...state,
|
|
@@ -286,7 +346,23 @@ function validateAgentIngress(context) {
|
|
|
286
346
|
});
|
|
287
347
|
if (rejected !== void 0) throw new A2Error("INVALID_PAYLOAD", `event '${rejected.type}' is server-only for an AI agent`);
|
|
288
348
|
}
|
|
289
|
-
const
|
|
349
|
+
const checkAbort = (signal) => {
|
|
350
|
+
if (!signal.aborted) return false;
|
|
351
|
+
const reason = signal.reason;
|
|
352
|
+
if (reason instanceof A2Error && (reason.code === "CLAIM_EXPIRED" || reason.code === "SUPERSEDED_ATTEMPT")) throw reason;
|
|
353
|
+
return true;
|
|
354
|
+
};
|
|
355
|
+
const resolve = async (value, context) => {
|
|
356
|
+
let result;
|
|
357
|
+
try {
|
|
358
|
+
result = typeof value === "function" ? await value(context) : value;
|
|
359
|
+
} catch (error) {
|
|
360
|
+
checkAbort(context.signal);
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
checkAbort(context.signal);
|
|
364
|
+
return result;
|
|
365
|
+
};
|
|
290
366
|
const modelName = (model) => {
|
|
291
367
|
if (typeof model === "string") return model;
|
|
292
368
|
if ("modelId" in model && typeof model.modelId === "string") return model.modelId;
|
|
@@ -325,6 +401,7 @@ async function generateWithAISDK(context, messageMetadata) {
|
|
|
325
401
|
model: context.model,
|
|
326
402
|
tools: context.tools,
|
|
327
403
|
messages: context.messages,
|
|
404
|
+
modelMessages: context.modelMessages,
|
|
328
405
|
responseMessageId: context.responseMessageId,
|
|
329
406
|
abortSignal: context.signal,
|
|
330
407
|
...context.instructions === void 0 ? {} : { instructions: context.instructions },
|
|
@@ -346,8 +423,8 @@ async function* consumeGeneration(options) {
|
|
|
346
423
|
const maxChunks = options.progress?.maxChunks ?? 16;
|
|
347
424
|
const maxDelayMs = options.progress?.maxDelayMs ?? 30;
|
|
348
425
|
let lastFlush = Date.now();
|
|
349
|
-
const
|
|
350
|
-
let pendingChunk =
|
|
426
|
+
const reader = options.source.stream.getReader();
|
|
427
|
+
let pendingChunk = reader.read();
|
|
351
428
|
try {
|
|
352
429
|
for (;;) {
|
|
353
430
|
let result;
|
|
@@ -369,7 +446,7 @@ async function* consumeGeneration(options) {
|
|
|
369
446
|
pending.push(chunk);
|
|
370
447
|
if (chunk.type === "finish") streamedFinishReason = chunk.finishReason;
|
|
371
448
|
if (chunk.type === "error") streamedError = chunk.errorText;
|
|
372
|
-
pendingChunk =
|
|
449
|
+
pendingChunk = reader.read();
|
|
373
450
|
if (pending.length >= maxChunks || isBoundaryChunk(pending.at(-1))) {
|
|
374
451
|
yield {
|
|
375
452
|
type: "progress",
|
|
@@ -384,6 +461,13 @@ async function* consumeGeneration(options) {
|
|
|
384
461
|
chunks: pending.splice(0)
|
|
385
462
|
};
|
|
386
463
|
throw error;
|
|
464
|
+
} finally {
|
|
465
|
+
pendingChunk.catch(() => {});
|
|
466
|
+
try {
|
|
467
|
+
await reader.cancel();
|
|
468
|
+
} catch {} finally {
|
|
469
|
+
reader.releaseLock();
|
|
470
|
+
}
|
|
387
471
|
}
|
|
388
472
|
let completion;
|
|
389
473
|
try {
|
|
@@ -412,20 +496,131 @@ const replay = (definition, history) => {
|
|
|
412
496
|
for (const event of history) state = definition.reducer.fold(state, event);
|
|
413
497
|
return state;
|
|
414
498
|
};
|
|
415
|
-
const
|
|
499
|
+
const summarize = async (options) => {
|
|
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
|
+
].filter(Boolean).join("\n");
|
|
506
|
+
const generation = { ...options.context.generation };
|
|
507
|
+
for (const key of Object.keys(generation)) if (key.startsWith("on") || key.startsWith("experimental_on") || [
|
|
508
|
+
"repairToolCall",
|
|
509
|
+
"experimental_repairToolCall",
|
|
510
|
+
"experimental_refineToolInput",
|
|
511
|
+
"experimental_transform",
|
|
512
|
+
"toolApproval"
|
|
513
|
+
].includes(key)) Reflect.deleteProperty(generation, key);
|
|
514
|
+
const tools = Object.fromEntries(Object.entries(options.context.tools).map(([name, tool]) => {
|
|
515
|
+
const definition = {
|
|
516
|
+
...tool,
|
|
517
|
+
needsApproval: false
|
|
518
|
+
};
|
|
519
|
+
for (const key of [
|
|
520
|
+
"execute",
|
|
521
|
+
"onInputStart",
|
|
522
|
+
"onInputDelta",
|
|
523
|
+
"onInputAvailable"
|
|
524
|
+
]) Reflect.deleteProperty(definition, key);
|
|
525
|
+
return [name, definition];
|
|
526
|
+
}));
|
|
527
|
+
const source = await generateWithAISDK({
|
|
528
|
+
...options.context,
|
|
529
|
+
generation,
|
|
530
|
+
tools,
|
|
531
|
+
modelMessages: [...options.context.modelMessages, {
|
|
532
|
+
role: "user",
|
|
533
|
+
content: instruction
|
|
534
|
+
}],
|
|
535
|
+
responseMessageId: `${options.context.generationId}:summary`
|
|
536
|
+
}, void 0);
|
|
537
|
+
let summary = "";
|
|
538
|
+
let calledTool = false;
|
|
539
|
+
let finish;
|
|
540
|
+
for await (const update of consumeGeneration({ source })) {
|
|
541
|
+
options.context.signal.throwIfAborted();
|
|
542
|
+
if (update.type === "finish") {
|
|
543
|
+
finish = update;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
for (const chunk of update.chunks) {
|
|
547
|
+
if (chunk.type === "text-delta") summary += chunk.delta;
|
|
548
|
+
if (chunk.type.startsWith("tool-")) calledTool = true;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (calledTool || summary.trim().length === 0 || finish?.finishReason !== "stop") throw new Error("compaction must produce a complete text summary without calling tools");
|
|
552
|
+
return {
|
|
553
|
+
summary,
|
|
554
|
+
...finish.usage === void 0 ? {} : { usage: finish.usage }
|
|
555
|
+
};
|
|
556
|
+
};
|
|
557
|
+
const activeCompaction = (state) => {
|
|
416
558
|
const compaction = state.compaction;
|
|
417
|
-
|
|
418
|
-
|
|
559
|
+
return compaction?.status === "completed" && compaction.messages !== void 0 && state.messages.some((message) => message.id === compaction.throughMessageId) ? compaction : null;
|
|
560
|
+
};
|
|
561
|
+
const contextMessages = (options) => {
|
|
562
|
+
const { agent, state, history } = options;
|
|
563
|
+
const compaction = activeCompaction(state);
|
|
564
|
+
if (compaction === null) return state.messages;
|
|
419
565
|
const retained = new Set(compaction.retainedMessageIds ?? []);
|
|
420
|
-
|
|
566
|
+
const startedIndex = history.find((event) => event.type === "ai.generation.started" && event.payload.generationId === compaction.generationId)?.index;
|
|
567
|
+
const throughIndex = compaction.throughIndex ?? (startedIndex === void 0 ? void 0 : startedIndex - 1);
|
|
568
|
+
if (throughIndex !== void 0) {
|
|
569
|
+
const prefix = history.filter((event) => event.index <= throughIndex);
|
|
570
|
+
for (const queued of queuedMessagesAt(prefix)) retained.add(queued.messageId);
|
|
571
|
+
let context = replay(agent, prefix);
|
|
572
|
+
context = {
|
|
573
|
+
...context,
|
|
574
|
+
messages: [...compaction.messages, ...context.messages.filter((message) => retained.has(message.id))],
|
|
575
|
+
activeProjection: null
|
|
576
|
+
};
|
|
577
|
+
for (const event of history) if (event.index > throughIndex) context = agent.reducer.fold(context, event);
|
|
578
|
+
return context.messages;
|
|
579
|
+
}
|
|
580
|
+
const boundary = state.messages.findIndex((message) => message.id === compaction.throughMessageId);
|
|
581
|
+
return [
|
|
421
582
|
...compaction.messages,
|
|
422
583
|
...state.messages.slice(0, boundary + 1).filter((message) => retained.has(message.id)),
|
|
423
584
|
...state.messages.slice(boundary + 1)
|
|
424
585
|
];
|
|
425
586
|
};
|
|
426
|
-
const activeContextMessages = (
|
|
427
|
-
const queued = new Set(coordinator.queued.map((item) => item.messageId));
|
|
428
|
-
return contextMessages(
|
|
587
|
+
const activeContextMessages = (options) => {
|
|
588
|
+
const queued = new Set(options.coordinator.queued.map((item) => item.messageId));
|
|
589
|
+
return contextMessages(options).filter((message) => !queued.has(message.id));
|
|
590
|
+
};
|
|
591
|
+
const modelContext = async (options) => {
|
|
592
|
+
const messages = await convertToModelMessages(options.messages, { tools: options.tools });
|
|
593
|
+
const summary = activeCompaction(options.state)?.summary;
|
|
594
|
+
return summary === void 0 ? messages : [{
|
|
595
|
+
role: "user",
|
|
596
|
+
content: summary
|
|
597
|
+
}, ...messages];
|
|
598
|
+
};
|
|
599
|
+
const estimateInputTokens = async (options) => {
|
|
600
|
+
const tools = await Promise.all(Object.entries(options.tools).map(async ([name, tool]) => ({
|
|
601
|
+
name,
|
|
602
|
+
description: tool.description,
|
|
603
|
+
inputSchema: await asSchema(tool.inputSchema).jsonSchema
|
|
604
|
+
})));
|
|
605
|
+
return Math.ceil(new TextEncoder().encode(JSON.stringify({
|
|
606
|
+
messages: options.messages,
|
|
607
|
+
instructions: options.instructions,
|
|
608
|
+
tools
|
|
609
|
+
})).byteLength / 4);
|
|
610
|
+
};
|
|
611
|
+
const measuredInputTokens = (options) => {
|
|
612
|
+
for (const event of options.history.toReversed()) {
|
|
613
|
+
if (event.type === "ai.compaction.completed" || event.type === "ai.retry.requested") break;
|
|
614
|
+
if (event.type !== "ai.generation.completed") continue;
|
|
615
|
+
const completed = event.payload;
|
|
616
|
+
const started = options.history.find((candidate) => candidate.type === "ai.generation.started" && candidate.payload.generationId === completed.generationId);
|
|
617
|
+
const input = completed.usage?.inputTokens;
|
|
618
|
+
const estimate = completed.inputTokenEstimate;
|
|
619
|
+
if ((started?.payload)?.model !== options.model) break;
|
|
620
|
+
if (input === void 0 || estimate === void 0 || !Number.isFinite(input) || input < 0 || options.estimate < estimate) break;
|
|
621
|
+
return Math.max(options.estimate, Math.ceil(input + options.estimate - estimate));
|
|
622
|
+
}
|
|
623
|
+
return options.estimate;
|
|
429
624
|
};
|
|
430
625
|
const generationRequestId = (generationId) => {
|
|
431
626
|
const markerIndex = generationId.lastIndexOf(":generation:");
|
|
@@ -639,6 +834,24 @@ function withoutGenerationLifecycle(history, generationIds) {
|
|
|
639
834
|
return true;
|
|
640
835
|
});
|
|
641
836
|
}
|
|
837
|
+
const validateCompaction = (options) => {
|
|
838
|
+
let compaction = options.compaction;
|
|
839
|
+
if (compaction !== false && (typeof compaction !== "object" || compaction === null)) throw new TypeError("compaction must resolve to false or a policy object");
|
|
840
|
+
if (compaction !== false && "then" in compaction) {
|
|
841
|
+
Promise.resolve(compaction).catch(() => {});
|
|
842
|
+
throw new TypeError("compaction options must resolve synchronously");
|
|
843
|
+
}
|
|
844
|
+
if (compaction !== false && !("shouldCompact" in compaction)) {
|
|
845
|
+
if (compaction.thresholdTokens !== void 0 && (!Number.isSafeInteger(compaction.thresholdTokens) || compaction.thresholdTokens < 1)) throw new TypeError("compaction.thresholdTokens must be a positive safe integer");
|
|
846
|
+
const choice = options.generation?.toolChoice;
|
|
847
|
+
const unsupported = options.generation?.output !== void 0 ? "structured output" : choice !== void 0 && choice !== "auto" && choice !== "none" ? "forced tool choice" : Object.values(options.tools ?? {}).some((tool) => tool.type === "provider" && tool.isProviderExecuted === true) ? "provider-executed tools" : void 0;
|
|
848
|
+
if (unsupported !== void 0) {
|
|
849
|
+
if (options.explicit) throw new TypeError(`automatic compaction does not support ${unsupported}; use a custom policy or compaction: false`);
|
|
850
|
+
compaction = false;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return compaction;
|
|
854
|
+
};
|
|
642
855
|
/**
|
|
643
856
|
* Build the ordinary A2 handler table for the built-in agent protocol.
|
|
644
857
|
* Application handlers can be spread beside this table.
|
|
@@ -648,6 +861,13 @@ function createHandlers(options) {
|
|
|
648
861
|
if (options.progress?.maxChunks !== void 0 && (!Number.isInteger(options.progress.maxChunks) || options.progress.maxChunks < 1)) throw new TypeError("progress.maxChunks must be a positive integer");
|
|
649
862
|
if (options.progress?.maxDelayMs !== void 0 && (!Number.isFinite(options.progress.maxDelayMs) || options.progress.maxDelayMs < 0)) throw new TypeError("progress.maxDelayMs must be a non-negative number");
|
|
650
863
|
if (options.maxSteps !== void 0 && options.maxSteps !== Number.POSITIVE_INFINITY && (!Number.isInteger(options.maxSteps) || options.maxSteps < 1)) throw new TypeError("maxSteps must be a positive integer or Infinity");
|
|
864
|
+
const configuredCompaction = options.compaction ?? (options.generate === void 0 ? {} : false);
|
|
865
|
+
const staticCompaction = typeof configuredCompaction === "function" ? void 0 : validateCompaction({
|
|
866
|
+
compaction: configuredCompaction,
|
|
867
|
+
explicit: options.compaction !== void 0,
|
|
868
|
+
generation: options.generation,
|
|
869
|
+
tools: options.tools
|
|
870
|
+
});
|
|
651
871
|
const tools = options.tools ?? {};
|
|
652
872
|
const generation = options.generation ?? {};
|
|
653
873
|
const maxSteps = options.maxSteps ?? Number.POSITIVE_INFINITY;
|
|
@@ -729,15 +949,20 @@ function createHandlers(options) {
|
|
|
729
949
|
if (cached) return cached;
|
|
730
950
|
const computation = (async () => {
|
|
731
951
|
const history = await readHistory();
|
|
732
|
-
const
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
952
|
+
const started = history.find((event) => event.type === "ai.generation.started" && event.payload.generationId === call.generationId);
|
|
953
|
+
const frontier = (started?.payload)?.promptThroughIndex ?? (started?.index ?? Number.POSITIVE_INFINITY) - 1;
|
|
954
|
+
const promptHistory = history.filter((event) => event.index <= frontier || (event.type === "ai.generation.started" || event.type === "ai.compaction.requested" || event.type === "ai.compaction.completed") && event.payload.generationId === call.generationId);
|
|
955
|
+
const state = replay(options.agent, promptHistory);
|
|
956
|
+
return modelContext({
|
|
957
|
+
messages: activeContextMessages({
|
|
958
|
+
agent: options.agent,
|
|
959
|
+
state,
|
|
960
|
+
history: promptHistory,
|
|
961
|
+
coordinator: coordinatorStateAt(promptHistory)
|
|
962
|
+
}),
|
|
963
|
+
state,
|
|
964
|
+
tools
|
|
965
|
+
});
|
|
741
966
|
})();
|
|
742
967
|
promptCache.set(key, computation);
|
|
743
968
|
computation.catch(() => {
|
|
@@ -747,7 +972,7 @@ function createHandlers(options) {
|
|
|
747
972
|
};
|
|
748
973
|
const toolExecutionFailure = (ctx, call, error) => {
|
|
749
974
|
const schedulerFailure = consumeSchedulerSendFailure(error);
|
|
750
|
-
if (ctx.signal
|
|
975
|
+
if (checkAbort(ctx.signal)) return;
|
|
751
976
|
if (schedulerFailure === "retryable") throw error;
|
|
752
977
|
return resultEvent(call, "execution:error", { error: errorMessage(error) });
|
|
753
978
|
};
|
|
@@ -771,33 +996,31 @@ function createHandlers(options) {
|
|
|
771
996
|
if (iterator === void 0) return resultEvent(call, "execution:0", { output });
|
|
772
997
|
let last;
|
|
773
998
|
let sequence = 0;
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
result
|
|
778
|
-
} catch (error) {
|
|
779
|
-
return toolExecutionFailure(ctx, call, error);
|
|
780
|
-
}
|
|
781
|
-
if (result.done) break;
|
|
782
|
-
if (ctx.signal.aborted) {
|
|
999
|
+
let done = false;
|
|
1000
|
+
try {
|
|
1001
|
+
for (;;) {
|
|
1002
|
+
let result;
|
|
783
1003
|
try {
|
|
784
|
-
await iterator.
|
|
785
|
-
} catch {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1004
|
+
result = await iterator.next();
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
return toolExecutionFailure(ctx, call, error);
|
|
1007
|
+
}
|
|
1008
|
+
if (checkAbort(ctx.signal)) return;
|
|
1009
|
+
if (result.done) {
|
|
1010
|
+
done = true;
|
|
1011
|
+
break;
|
|
1012
|
+
}
|
|
1013
|
+
last = result.value;
|
|
1014
|
+
await ctx.session.append(`tool:${call.toolCallId}:preliminary:${sequence}`, resultEvent(call, `execution:${ctx.attempt}:${sequence}:preliminary`, {
|
|
791
1015
|
output: result.value,
|
|
792
1016
|
preliminary: true
|
|
793
1017
|
}));
|
|
794
|
-
|
|
795
|
-
try {
|
|
796
|
-
await iterator.return?.();
|
|
797
|
-
} catch {}
|
|
798
|
-
throw error;
|
|
1018
|
+
sequence += 1;
|
|
799
1019
|
}
|
|
800
|
-
|
|
1020
|
+
} finally {
|
|
1021
|
+
if (!done) try {
|
|
1022
|
+
await iterator.return?.();
|
|
1023
|
+
} catch {}
|
|
801
1024
|
}
|
|
802
1025
|
return resultEvent(call, `execution:${sequence}:final`, sequence === 0 ? {} : { output: last });
|
|
803
1026
|
};
|
|
@@ -805,7 +1028,7 @@ function createHandlers(options) {
|
|
|
805
1028
|
const execute = tools[call.toolName]?.execute;
|
|
806
1029
|
if (execute === void 0) return resultEvent(call, "execution:error", { error: `Tool '${call.toolName}' has no server executor` });
|
|
807
1030
|
const messages = await promptMessages(ctx.event.sessionId, call, ctx.session.history);
|
|
808
|
-
if (ctx.signal
|
|
1031
|
+
if (checkAbort(ctx.signal)) return;
|
|
809
1032
|
const scope = {
|
|
810
1033
|
contract: options.agent.contract,
|
|
811
1034
|
context: ctx
|
|
@@ -863,120 +1086,211 @@ function createHandlers(options) {
|
|
|
863
1086
|
if (incompleteId !== void 0) replacedGenerationIds.add(incompleteId);
|
|
864
1087
|
if (request.reason === "retry" && sourceCoordinatorState.response?.failure?.generationId !== void 0) replacedGenerationIds.add(sourceCoordinatorState.response.failure.generationId);
|
|
865
1088
|
const promptHistory = withoutGenerationLifecycle(history, replacedGenerationIds);
|
|
866
|
-
|
|
1089
|
+
const state = replay(options.agent, promptHistory);
|
|
867
1090
|
const resolverContext = {
|
|
868
1091
|
event: ctx.event,
|
|
869
1092
|
state,
|
|
870
1093
|
history,
|
|
871
1094
|
signal: ctx.signal
|
|
872
1095
|
};
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
return
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const
|
|
1096
|
+
let generationStarted = false;
|
|
1097
|
+
try {
|
|
1098
|
+
const resolvedModel = await resolve(options.model, resolverContext);
|
|
1099
|
+
if (resolvedModel === void 0) throw new TypeError("the model resolver returned undefined");
|
|
1100
|
+
if (checkAbort(ctx.signal)) return;
|
|
1101
|
+
const resolvedInstructions = options.instructions === void 0 ? void 0 : await resolve(options.instructions, resolverContext);
|
|
1102
|
+
if (checkAbort(ctx.signal)) return;
|
|
1103
|
+
const compaction = typeof configuredCompaction === "function" ? validateCompaction({
|
|
1104
|
+
compaction: configuredCompaction(resolverContext),
|
|
1105
|
+
explicit: true,
|
|
1106
|
+
generation: options.generation,
|
|
1107
|
+
tools: options.tools
|
|
1108
|
+
}) : staticCompaction;
|
|
1109
|
+
if (checkAbort(ctx.signal)) return;
|
|
1110
|
+
if (request.reason === "tool" && responseStepCount >= maxSteps) {
|
|
1111
|
+
const source = sourceCoordinatorState.response?.generation;
|
|
1112
|
+
if (source === void 0) return;
|
|
1113
|
+
return {
|
|
1114
|
+
type: "ai.generation.failed",
|
|
1115
|
+
id: `${requestId}:step-limit`,
|
|
1116
|
+
payload: {
|
|
1117
|
+
requestId: source.requestId,
|
|
1118
|
+
messageId: source.messageId,
|
|
1119
|
+
generationId: source.generationId,
|
|
1120
|
+
responseMessageId,
|
|
1121
|
+
error: `agent exceeded the ${maxSteps}-step limit`,
|
|
1122
|
+
stepLimit: true
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
const started = {
|
|
904
1127
|
requestId,
|
|
905
|
-
messageId:
|
|
906
|
-
generationId
|
|
907
|
-
responseMessageId
|
|
908
|
-
|
|
909
|
-
|
|
1128
|
+
messageId: request.messageId,
|
|
1129
|
+
generationId,
|
|
1130
|
+
responseMessageId,
|
|
1131
|
+
attempt,
|
|
1132
|
+
model: modelName(resolvedModel),
|
|
1133
|
+
promptThroughIndex: history.at(-1)?.index ?? ctx.event.index
|
|
910
1134
|
};
|
|
1135
|
+
const catalogModelId = gatewayModelId(resolvedModel);
|
|
1136
|
+
const gatewayOptions = options.generation?.providerOptions?.["gateway"];
|
|
1137
|
+
const usesFallbackModels = Array.isArray(gatewayOptions?.["models"]) && gatewayOptions["models"].length > 0;
|
|
1138
|
+
const discoversMetadata = compaction !== false && !("shouldCompact" in compaction) && compaction.thresholdTokens === void 0 && !usesFallbackModels && catalogModelId !== void 0;
|
|
1139
|
+
const startEvents = [];
|
|
1140
|
+
if (discoversMetadata && !Object.hasOwn(state.modelMetadata, catalogModelId)) startEvents.push({
|
|
1141
|
+
type: "ai.model.metadata.requested",
|
|
1142
|
+
id: `ai.model.metadata:${encodeURIComponent(options.agent.contract.name)}:${encodeURIComponent(ctx.event.sessionId)}:${encodeURIComponent(catalogModelId)}`,
|
|
1143
|
+
payload: { modelId: catalogModelId }
|
|
1144
|
+
});
|
|
1145
|
+
if (previous) {
|
|
1146
|
+
const payload = previous.payload;
|
|
1147
|
+
const superseded = {
|
|
1148
|
+
requestId,
|
|
1149
|
+
messageId: payload.messageId,
|
|
1150
|
+
generationId: payload.generationId,
|
|
1151
|
+
responseMessageId: payload.responseMessageId,
|
|
1152
|
+
error: "generation attempt was superseded after an incomplete run",
|
|
1153
|
+
superseded: true
|
|
1154
|
+
};
|
|
1155
|
+
startEvents.push({
|
|
1156
|
+
type: "ai.generation.failed",
|
|
1157
|
+
id: `${payload.generationId}:superseded`,
|
|
1158
|
+
payload: superseded
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
911
1161
|
startEvents.push({
|
|
912
|
-
type: "ai.generation.
|
|
913
|
-
id:
|
|
914
|
-
payload:
|
|
1162
|
+
type: "ai.generation.started",
|
|
1163
|
+
id: generationId,
|
|
1164
|
+
payload: started
|
|
915
1165
|
});
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const startedCoordinatorState = (await ctx.session.state(coordinator, { through: "latest" })).state;
|
|
924
|
-
if (startedCoordinatorState.response?.activeRequestId !== requestId || startedCoordinatorState.response.generation?.generationId !== generationId) return;
|
|
925
|
-
const promptCoordinatorState = {
|
|
926
|
-
...coordinatorStateAt(history),
|
|
927
|
-
queued: queuedMessagesAt(history)
|
|
928
|
-
};
|
|
929
|
-
let messages = activeContextMessages(state, promptCoordinatorState);
|
|
930
|
-
if (options.compaction) {
|
|
931
|
-
const compactionContext = {
|
|
932
|
-
...resolverContext,
|
|
933
|
-
messages
|
|
1166
|
+
const generationHistory = [...history, ...await ctx.session.append("generation-start", ...startEvents)];
|
|
1167
|
+
generationStarted = true;
|
|
1168
|
+
const startedCoordinatorState = (await ctx.session.state(coordinator, { through: "latest" })).state;
|
|
1169
|
+
if (startedCoordinatorState.response?.activeRequestId !== requestId || startedCoordinatorState.response.generation?.generationId !== generationId) return;
|
|
1170
|
+
const promptCoordinatorState = {
|
|
1171
|
+
...coordinatorStateAt(history),
|
|
1172
|
+
queued: queuedMessagesAt(history)
|
|
934
1173
|
};
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1174
|
+
const messages = activeContextMessages({
|
|
1175
|
+
agent: options.agent,
|
|
1176
|
+
state,
|
|
1177
|
+
history: promptHistory,
|
|
1178
|
+
coordinator: promptCoordinatorState
|
|
1179
|
+
});
|
|
1180
|
+
let modelMessages = await modelContext({
|
|
1181
|
+
messages,
|
|
1182
|
+
state,
|
|
1183
|
+
tools
|
|
1184
|
+
});
|
|
1185
|
+
const baseContext = {
|
|
1186
|
+
request: ctx.event,
|
|
1187
|
+
requestId,
|
|
1188
|
+
generationId,
|
|
1189
|
+
responseMessageId,
|
|
1190
|
+
messages,
|
|
1191
|
+
modelMessages,
|
|
1192
|
+
state,
|
|
1193
|
+
history,
|
|
1194
|
+
signal: ctx.signal,
|
|
1195
|
+
model: resolvedModel,
|
|
1196
|
+
tools,
|
|
1197
|
+
...resolvedInstructions === void 0 ? {} : { instructions: resolvedInstructions },
|
|
1198
|
+
generation
|
|
1199
|
+
};
|
|
1200
|
+
const policy = compaction;
|
|
1201
|
+
const tracksInput = policy !== false && !("shouldCompact" in policy) && (policy.thresholdTokens !== void 0 || discoversMetadata);
|
|
1202
|
+
const metadata = catalogModelId === void 0 ? void 0 : state.modelMetadata[catalogModelId];
|
|
1203
|
+
const limits = metadata?.status === "resolved" && !usesFallbackModels ? metadata.limits : void 0;
|
|
1204
|
+
let inputTokenEstimate;
|
|
1205
|
+
let compacted = false;
|
|
1206
|
+
const canCompact = sourceCoordinatorState.response?.calls.every((call) => call.terminal) ?? true;
|
|
1207
|
+
if (policy && canCompact) {
|
|
1208
|
+
const compactionContext = {
|
|
1209
|
+
...resolverContext,
|
|
949
1210
|
messages,
|
|
950
|
-
|
|
951
|
-
};
|
|
952
|
-
await ctx.session.append("compaction-completed", {
|
|
953
|
-
type: "ai.compaction.completed",
|
|
954
|
-
id: `${generationId}:compaction:completed`,
|
|
955
|
-
payload: completed
|
|
956
|
-
});
|
|
957
|
-
state = {
|
|
958
|
-
...state,
|
|
959
|
-
compaction: {
|
|
960
|
-
status: "completed",
|
|
961
|
-
...completed
|
|
962
|
-
}
|
|
1211
|
+
modelMessages
|
|
963
1212
|
};
|
|
1213
|
+
let shouldCompact;
|
|
1214
|
+
if ("shouldCompact" in policy) shouldCompact = await policy.shouldCompact(compactionContext);
|
|
1215
|
+
else if (tracksInput) {
|
|
1216
|
+
inputTokenEstimate = await estimateInputTokens({
|
|
1217
|
+
messages: modelMessages,
|
|
1218
|
+
instructions: resolvedInstructions,
|
|
1219
|
+
tools
|
|
1220
|
+
});
|
|
1221
|
+
const inputTokens = measuredInputTokens({
|
|
1222
|
+
estimate: inputTokenEstimate,
|
|
1223
|
+
model: modelName(resolvedModel),
|
|
1224
|
+
history
|
|
1225
|
+
});
|
|
1226
|
+
const threshold = policy.thresholdTokens ?? (limits === void 0 ? void 0 : Math.floor(Math.min(limits.contextWindow * .75, limits.contextWindow - (generation.maxOutputTokens ?? 0))));
|
|
1227
|
+
if (limits !== void 0 && threshold !== void 0 && threshold <= 0) throw new Error(`output allowance exhausts the ${limits.contextWindow}-token context window`);
|
|
1228
|
+
shouldCompact = threshold !== void 0 && inputTokens >= threshold;
|
|
1229
|
+
} else shouldCompact = false;
|
|
1230
|
+
if (checkAbort(ctx.signal)) return;
|
|
1231
|
+
if (shouldCompact) {
|
|
1232
|
+
const throughMessageId = state.messages.findLast((message) => !promptCoordinatorState.queued.some((queued) => queued.messageId === message.id))?.id ?? request.messageId;
|
|
1233
|
+
const throughIndex = history.at(-1)?.index ?? ctx.event.index;
|
|
1234
|
+
generationHistory.push(...await ctx.session.append("compaction-requested", {
|
|
1235
|
+
type: "ai.compaction.requested",
|
|
1236
|
+
id: `${generationId}:compaction:requested`,
|
|
1237
|
+
payload: {
|
|
1238
|
+
generationId,
|
|
1239
|
+
throughMessageId,
|
|
1240
|
+
throughIndex
|
|
1241
|
+
}
|
|
1242
|
+
}));
|
|
1243
|
+
const result = !("shouldCompact" in policy) ? {
|
|
1244
|
+
messages: [],
|
|
1245
|
+
...await summarize({
|
|
1246
|
+
context: baseContext,
|
|
1247
|
+
instructions: policy.instructions
|
|
1248
|
+
})
|
|
1249
|
+
} : { messages: await policy.compact(compactionContext) };
|
|
1250
|
+
if (checkAbort(ctx.signal)) return;
|
|
1251
|
+
const completed = {
|
|
1252
|
+
generationId,
|
|
1253
|
+
throughMessageId,
|
|
1254
|
+
throughIndex,
|
|
1255
|
+
...result,
|
|
1256
|
+
...promptCoordinatorState.queued.length === 0 ? {} : { retainedMessageIds: promptCoordinatorState.queued.map((item) => item.messageId) }
|
|
1257
|
+
};
|
|
1258
|
+
generationHistory.push(...await ctx.session.append("compaction-completed", {
|
|
1259
|
+
type: "ai.compaction.completed",
|
|
1260
|
+
id: `${generationId}:compaction:completed`,
|
|
1261
|
+
payload: completed
|
|
1262
|
+
}));
|
|
1263
|
+
compacted = true;
|
|
1264
|
+
}
|
|
964
1265
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1266
|
+
const currentState = replay(options.agent, generationHistory);
|
|
1267
|
+
const currentCoordinatorState = coordinatorStateAt(generationHistory);
|
|
1268
|
+
const generationMessages = activeContextMessages({
|
|
1269
|
+
agent: options.agent,
|
|
1270
|
+
state: currentState,
|
|
1271
|
+
history: generationHistory,
|
|
1272
|
+
coordinator: currentCoordinatorState
|
|
1273
|
+
});
|
|
1274
|
+
modelMessages = await modelContext({
|
|
1275
|
+
messages: generationMessages,
|
|
1276
|
+
state: currentState,
|
|
1277
|
+
tools
|
|
1278
|
+
});
|
|
1279
|
+
promptCache.set(promptCacheKey(ctx.event.sessionId, generationId), Promise.resolve(modelMessages));
|
|
1280
|
+
if (tracksInput && (inputTokenEstimate === void 0 || compacted)) inputTokenEstimate = await estimateInputTokens({
|
|
1281
|
+
messages: modelMessages,
|
|
1282
|
+
instructions: resolvedInstructions,
|
|
1283
|
+
tools
|
|
1284
|
+
});
|
|
1285
|
+
let pendingToolCalls = [];
|
|
1286
|
+
if (checkAbort(ctx.signal)) return;
|
|
974
1287
|
const generateContext = {
|
|
975
1288
|
request: ctx.event,
|
|
976
1289
|
requestId,
|
|
977
1290
|
generationId,
|
|
978
1291
|
responseMessageId,
|
|
979
1292
|
messages: generationMessages,
|
|
1293
|
+
modelMessages,
|
|
980
1294
|
state: currentState,
|
|
981
1295
|
history,
|
|
982
1296
|
signal: ctx.signal,
|
|
@@ -1031,6 +1345,7 @@ function createHandlers(options) {
|
|
|
1031
1345
|
messageId: request.messageId,
|
|
1032
1346
|
generationId,
|
|
1033
1347
|
responseMessageId,
|
|
1348
|
+
...inputTokenEstimate === void 0 ? {} : { inputTokenEstimate },
|
|
1034
1349
|
...finish.finishReason === void 0 ? {} : { finishReason: finish.finishReason },
|
|
1035
1350
|
...finish.usage === void 0 ? {} : { usage: finish.usage }
|
|
1036
1351
|
};
|
|
@@ -1053,8 +1368,11 @@ function createHandlers(options) {
|
|
|
1053
1368
|
}
|
|
1054
1369
|
];
|
|
1055
1370
|
} catch (error) {
|
|
1056
|
-
if (error instanceof A2Error)
|
|
1057
|
-
|
|
1371
|
+
if (error instanceof A2Error) {
|
|
1372
|
+
checkAbort(ctx.signal);
|
|
1373
|
+
throw error;
|
|
1374
|
+
}
|
|
1375
|
+
if (checkAbort(ctx.signal)) {
|
|
1058
1376
|
const interrupted = {
|
|
1059
1377
|
messageId: responseMessageId,
|
|
1060
1378
|
generationId,
|
|
@@ -1066,6 +1384,7 @@ function createHandlers(options) {
|
|
|
1066
1384
|
payload: interrupted
|
|
1067
1385
|
};
|
|
1068
1386
|
}
|
|
1387
|
+
if (!generationStarted) throw error;
|
|
1069
1388
|
const failed = {
|
|
1070
1389
|
requestId,
|
|
1071
1390
|
messageId: request.messageId,
|
|
@@ -1111,6 +1430,21 @@ function createHandlers(options) {
|
|
|
1111
1430
|
return scheduleNext(ctx);
|
|
1112
1431
|
};
|
|
1113
1432
|
return {
|
|
1433
|
+
"ai.model.metadata.requested": { handler: async (ctx) => {
|
|
1434
|
+
const limits = await readModelLimits({
|
|
1435
|
+
modelId: ctx.event.payload.modelId,
|
|
1436
|
+
signal: ctx.signal
|
|
1437
|
+
});
|
|
1438
|
+
ctx.signal.throwIfAborted();
|
|
1439
|
+
return {
|
|
1440
|
+
type: "ai.model.metadata.resolved",
|
|
1441
|
+
id: `${ctx.event.id}:resolved`,
|
|
1442
|
+
payload: {
|
|
1443
|
+
modelId: ctx.event.payload.modelId,
|
|
1444
|
+
limits
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
} },
|
|
1114
1448
|
"ai.message.created": {
|
|
1115
1449
|
lane: "a2.ai.turn",
|
|
1116
1450
|
handler: handleMessageCreated
|