experimental-a2 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/{actor-HnGmrOZZ.d.ts → actor-CqibLgjQ.d.ts} +13 -5
- package/dist/actor-CqibLgjQ.d.ts.map +1 -0
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-react.d.ts +1 -1
- package/dist/actor-react.js +1 -1
- package/dist/actor-shared-DFET5q16.js.map +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +3 -3
- package/dist/{ai-CvEnqVfo.d.ts → ai-DAu77CDr.d.ts} +2 -2
- package/dist/{ai-CvEnqVfo.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
- package/dist/{ai-CzE672Q3.js → ai-EEfKyc-z.js} +2 -2
- package/dist/{ai-CzE672Q3.js.map → ai-EEfKyc-z.js.map} +1 -1
- package/dist/ai-client.d.ts +1 -1
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.d.ts +4 -2
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +211 -61
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-Bfk-Lycn.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
- package/dist/{ai-harness-Bfk-Lycn.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
- package/dist/ai-harness-dQeJaR1t.js.map +1 -1
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.js +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +3 -3
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +10 -4
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +1 -1
- package/dist/ai.js +1 -1
- package/dist/{client-9IeTsiAs.js → client-BQS4i_V9.js} +27 -4
- package/dist/client-BQS4i_V9.js.map +1 -0
- package/dist/client-CUMbS_38.d.ts.map +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/{internal-Bc4PvfHk.js → internal-CvsrprnU.js} +32 -15
- package/dist/{internal-Bc4PvfHk.js.map → internal-CvsrprnU.js.map} +1 -1
- package/dist/react.js +1 -1
- package/dist/{runtime-consumer-BWmht60z.js → runtime-consumer-CIeL33wj.js} +2 -2
- package/dist/{runtime-consumer-BWmht60z.js.map → runtime-consumer-CIeL33wj.js.map} +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +2 -2
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +2 -2
- package/dist/{server-DvIB8kj5.js → server-DtFrWxi3.js} +30 -8
- package/dist/server-DtFrWxi3.js.map +1 -0
- package/dist/{server-BshFZ0eL.d.ts → server-qgez0Xny.d.ts} +3 -3
- package/dist/{server-BshFZ0eL.d.ts.map → server-qgez0Xny.d.ts.map} +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/{session-clear-Bk_29eEw.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
- package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
- package/dist/{store-Z2qjw50n.d.ts → store-D0MhBvg6.d.ts} +15 -6
- package/dist/store-D0MhBvg6.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +15 -17
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-DYNyxCev.js → store-polling-BZt2Rf3D.js} +2 -2
- package/dist/{store-polling-DYNyxCev.js.map → store-polling-BZt2Rf3D.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +31 -40
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-presence-polling-JuAy2Ck2.js +111 -0
- package/dist/store-presence-polling-JuAy2Ck2.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +3 -3
- package/dist/{store-redis-notify-IiVdlYHc.js → store-redis-notify-CsJw_hfD.js} +538 -397
- package/dist/store-redis-notify-CsJw_hfD.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +3 -4
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +16 -18
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-DrTyiIuo.js +57 -0
- package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
- package/dist/{wire-CZGqNZRx.js → wire-CV-Z7wz-.js} +3 -3
- package/dist/{wire-CZGqNZRx.js.map → wire-CV-Z7wz-.js.map} +1 -1
- package/docs/actors/01-introduction.mdx +5 -2
- package/docs/agents/03-conversations.mdx +9 -8
- package/docs/agents/04-harnesses.mdx +7 -4
- package/docs/reference/01-core.mdx +3 -2
- package/docs/reference/02-server.mdx +5 -1
- package/docs/reference/04-ai.mdx +13 -4
- package/docs/reference/06-errors.mdx +1 -1
- package/docs/server/01-consumers.mdx +4 -1
- package/docs/server/06-guarantees.mdx +7 -4
- package/docs/stores/02-redis.mdx +16 -10
- package/docs/stores/03-redis-http.mdx +6 -4
- package/docs/stores/04-postgres.mdx +2 -2
- package/docs/upgrades/01-overview.mdx +1 -0
- package/docs/upgrades/78-0-22.mdx +115 -0
- package/examples/playground/app/a0/sandbox.ts +58 -13
- package/examples/playground/app/a2slack/db/a2.ts +7 -4
- package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
- package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +8 -1
- package/examples/playground/app/chat/server.ts +2 -2
- package/examples/playground/lib/store.ts +1 -1
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/sql/postgres/drizzle-schema.ts +7 -4
- package/sql/postgres/schema.sql +24 -3
- package/src/actor-shared.ts +10 -2
- package/src/ai-agent.ts +13 -0
- package/src/ai-fx.ts +321 -77
- package/src/ai-harness.ts +7 -0
- package/src/client.ts +19 -2
- package/src/deterministic-id.ts +1 -1
- package/src/internal.ts +42 -11
- package/src/presence-recovery.ts +18 -11
- package/src/presence-tombstones.ts +52 -0
- package/src/push-envelope.ts +2 -2
- package/src/server.ts +49 -15
- package/src/store-memory.ts +24 -32
- package/src/store-postgres.ts +43 -53
- package/src/store-presence-polling.ts +59 -34
- package/src/store-redis-core.ts +595 -439
- package/src/store-redis-notify.ts +74 -26
- package/src/store-redis.ts +2 -3
- package/src/store-sqlite.ts +34 -40
- package/src/store.ts +14 -5
- package/dist/actor-HnGmrOZZ.d.ts.map +0 -1
- package/dist/client-9IeTsiAs.js.map +0 -1
- package/dist/server-DvIB8kj5.js.map +0 -1
- package/dist/session-clear-Bk_29eEw.d.ts.map +0 -1
- package/dist/store-Z2qjw50n.d.ts.map +0 -1
- package/dist/store-presence-polling-CzQ0wdtM.js +0 -94
- package/dist/store-presence-polling-CzQ0wdtM.js.map +0 -1
- package/dist/store-redis-notify-IiVdlYHc.js.map +0 -1
- package/dist/stream-buffer-Cx7AAPGq.js +0 -23
- package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
package/dist/ai-client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { s as CreateClientOptions, t as A2Client } from "./client-CUMbS_38.js";
|
|
2
|
-
import { a as AI_STATE_REDUCER, i as AIState, r as AIMessageOf, t as AIEventDefs } from "./ai-
|
|
2
|
+
import { a as AI_STATE_REDUCER, i as AIState, r as AIMessageOf, t as AIEventDefs } from "./ai-DAu77CDr.js";
|
|
3
3
|
//#region src/ai-client.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* The structural shape of `typeof` an `agent({ name })` server: one whose
|
package/dist/ai-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as createClient } from "./client-
|
|
2
|
-
import { n as agentContract } from "./ai-
|
|
1
|
+
import { t as createClient } from "./client-BQS4i_V9.js";
|
|
2
|
+
import { n as agentContract } from "./ai-EEfKyc-z.js";
|
|
3
3
|
//#region src/ai-client.ts
|
|
4
4
|
/**
|
|
5
5
|
* experimental-a2/ai/client — a client for an `agent()` route that needs no
|
package/dist/ai-fx.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { t as Harness } from "./ai-harness-
|
|
1
|
+
import { t as Harness } from "./ai-harness-DTjkAr2T.js";
|
|
2
2
|
//#region src/ai-fx.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* The FX harness. Every model call FX makes is one durable step; A2 rebuilds
|
|
5
|
-
* the kernel for each step from its latest checkpoint and the log since
|
|
5
|
+
* the kernel for each step from its latest checkpoint and the log since,
|
|
6
|
+
* unless this process holds the kernel the step before left waiting on its
|
|
7
|
+
* tool calls.
|
|
6
8
|
*/
|
|
7
9
|
declare function fx(): Harness<"fx", never>;
|
|
8
10
|
//#endregion
|
package/dist/ai-fx.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-fx.d.ts","names":[],"sources":["../src/ai-fx.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ai-fx.d.ts","names":[],"sources":["../src/ai-fx.ts"],"mappings":";;;;;;;;iBA+CgB,MAAM"}
|
package/dist/ai-fx.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { n as HARNESS_PARKING } from "./internal-CvsrprnU.js";
|
|
1
2
|
import { t as boundedCache } from "./bounded-cache-CPHWF-8h.js";
|
|
2
3
|
import { a as gatewaySessionAffinity, d as readHarness, i as gatewayCredential, n as deferred, p as saveHarness, r as defineHarness, s as harnessImages, t as Divergence, u as pending } from "./ai-harness-dQeJaR1t.js";
|
|
3
4
|
import { t as schemaInput } from "./ai-tool-input-DmSqMUQi.js";
|
|
@@ -9,12 +10,27 @@ import { asSchema } from "ai";
|
|
|
9
10
|
*/
|
|
10
11
|
/**
|
|
11
12
|
* The FX harness. Every model call FX makes is one durable step; A2 rebuilds
|
|
12
|
-
* the kernel for each step from its latest checkpoint and the log since
|
|
13
|
+
* the kernel for each step from its latest checkpoint and the log since,
|
|
14
|
+
* unless this process holds the kernel the step before left waiting on its
|
|
15
|
+
* tool calls.
|
|
13
16
|
*/
|
|
14
17
|
function fx() {
|
|
18
|
+
const lots = /* @__PURE__ */ new WeakMap();
|
|
19
|
+
const lotOf = (scope) => {
|
|
20
|
+
let lot = lots.get(scope);
|
|
21
|
+
if (lot === void 0) {
|
|
22
|
+
lot = parkingLot();
|
|
23
|
+
lots.set(scope, lot);
|
|
24
|
+
}
|
|
25
|
+
return lot;
|
|
26
|
+
};
|
|
15
27
|
return defineHarness("fx", {
|
|
16
28
|
tools: {},
|
|
17
|
-
step: runStep
|
|
29
|
+
step: (step) => runStep(step, lotOf(step.scope)),
|
|
30
|
+
release: ({ scope, sessionId }) => {
|
|
31
|
+
const entry = lots.get(scope)?.take(sessionId);
|
|
32
|
+
if (entry !== void 0) closeParked(entry);
|
|
33
|
+
}
|
|
18
34
|
});
|
|
19
35
|
}
|
|
20
36
|
const SNAPSHOT_VERSION = 1;
|
|
@@ -27,10 +43,52 @@ function fail(state, error) {
|
|
|
27
43
|
throw state.failure;
|
|
28
44
|
}
|
|
29
45
|
const diverged = (message) => new Divergence("FX", message);
|
|
30
|
-
|
|
46
|
+
function closeParked(entry) {
|
|
47
|
+
entry.fxTurn.cancel();
|
|
48
|
+
entry.kernel.agent.close().catch(() => {});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* One agent's parked kernels: one per session, at most `maxKernels`. Each is
|
|
52
|
+
* closed when its session has nothing left to run in this process (the
|
|
53
|
+
* agent's release), when its turn ends, or when another takes its place.
|
|
54
|
+
*/
|
|
55
|
+
function parkingLot() {
|
|
56
|
+
const held = /* @__PURE__ */ new Map();
|
|
57
|
+
const take = (sessionId) => {
|
|
58
|
+
const entry = held.get(sessionId);
|
|
59
|
+
held.delete(sessionId);
|
|
60
|
+
return entry;
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
take,
|
|
64
|
+
park(sessionId, entry) {
|
|
65
|
+
const replaced = take(sessionId);
|
|
66
|
+
if (replaced !== void 0) closeParked(replaced);
|
|
67
|
+
held.set(sessionId, entry);
|
|
68
|
+
const ended = () => {
|
|
69
|
+
if (held.get(sessionId) !== entry) return;
|
|
70
|
+
held.delete(sessionId);
|
|
71
|
+
closeParked(entry);
|
|
72
|
+
};
|
|
73
|
+
entry.fxTurn.result.then(ended, ended);
|
|
74
|
+
const oldest = held.keys().next().value;
|
|
75
|
+
if (held.size > HARNESS_PARKING.maxKernels && oldest !== void 0) {
|
|
76
|
+
const evicted = take(oldest);
|
|
77
|
+
if (evicted !== void 0) closeParked(evicted);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function runStep(step, lot) {
|
|
31
83
|
const { createFxAgent } = await loadLibfx();
|
|
32
84
|
const credential = gatewayCredential("fx");
|
|
33
85
|
const name = `${step.agentName}.a2.harness.fx`;
|
|
86
|
+
const definitions = await toolDefinitions(step.tools);
|
|
87
|
+
const settings = JSON.stringify([
|
|
88
|
+
kernelModel(step),
|
|
89
|
+
step.instructions,
|
|
90
|
+
definitions
|
|
91
|
+
]);
|
|
34
92
|
const { replayAll, ...first } = await readHarness({
|
|
35
93
|
step,
|
|
36
94
|
name,
|
|
@@ -41,16 +99,22 @@ async function runStep(step) {
|
|
|
41
99
|
let stopped = false;
|
|
42
100
|
const released = new AbortController();
|
|
43
101
|
const network = AbortSignal.any([step.signal, released.signal]);
|
|
44
|
-
let
|
|
102
|
+
let kernel;
|
|
103
|
+
/** Whether this step left its kernel for the next one. */
|
|
104
|
+
let parked = false;
|
|
45
105
|
let turn;
|
|
46
|
-
const run = async (controller, turns) => {
|
|
106
|
+
const run = async (controller, turns, adopted) => {
|
|
47
107
|
controller.enqueue({
|
|
48
108
|
type: "start",
|
|
49
109
|
messageId: step.responseMessageId
|
|
50
110
|
});
|
|
111
|
+
if (adopted !== void 0) {
|
|
112
|
+
await resume(controller, turns.at(-1), adopted);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
51
115
|
for (const plan of turns) {
|
|
52
|
-
if (stopped) return;
|
|
53
|
-
const fxTurn = agent.prompt(plan.prompt
|
|
116
|
+
if (stopped || step.signal.aborted) return;
|
|
117
|
+
const fxTurn = kernel.agent.prompt(plan.prompt);
|
|
54
118
|
const state = {
|
|
55
119
|
plan,
|
|
56
120
|
fxTurn,
|
|
@@ -76,29 +140,82 @@ async function runStep(step) {
|
|
|
76
140
|
if (result.stopReason !== expected) throw diverged(`a recorded turn ended with ${result.stopReason}, not ${expected}`);
|
|
77
141
|
continue;
|
|
78
142
|
}
|
|
79
|
-
|
|
80
|
-
ended: true,
|
|
81
|
-
result
|
|
82
|
-
})), state.toolsRequested.promise.then(() => ({ ended: false }))]).catch((error) => {
|
|
83
|
-
throw state.failure ?? error;
|
|
84
|
-
});
|
|
85
|
-
if (state.failure) throw state.failure;
|
|
86
|
-
await state.parsed;
|
|
87
|
-
if (outcome.ended) {
|
|
88
|
-
await drained;
|
|
89
|
-
if (outcome.result.stopReason === "end_turn" && !step.signal.aborted) await saveCheckpoint();
|
|
90
|
-
} else fxTurn.cancel();
|
|
91
|
-
controller.enqueue({ type: "finish-step" });
|
|
92
|
-
controller.enqueue({
|
|
93
|
-
type: "finish",
|
|
94
|
-
...completion.finishReason === void 0 ? {} : { finishReason: completion.finishReason }
|
|
95
|
-
});
|
|
143
|
+
await finishLive(controller, state, drained);
|
|
96
144
|
}
|
|
97
145
|
};
|
|
146
|
+
/** Continues a parked kernel: the results it waits on are in the log. */
|
|
147
|
+
const resume = async (controller, plan, adopted) => {
|
|
148
|
+
const state = {
|
|
149
|
+
plan,
|
|
150
|
+
fxTurn: adopted.fxTurn,
|
|
151
|
+
next: plan.calls.length,
|
|
152
|
+
served: plan.calls.at(-1).toolCalls,
|
|
153
|
+
answered: /* @__PURE__ */ new Set(),
|
|
154
|
+
live: false,
|
|
155
|
+
liveStep: false,
|
|
156
|
+
toolsRequested: deferred()
|
|
157
|
+
};
|
|
158
|
+
turn = state;
|
|
159
|
+
for (const text of plan.steers.get(plan.calls.length - 1) ?? []) await adopted.fxTurn.steer(text);
|
|
160
|
+
for (const waiting of kernel.waiting.splice(0)) try {
|
|
161
|
+
const answered = answer(state, waiting.toolName, waiting.input);
|
|
162
|
+
if (answered === void 0) fail(state, diverged(`no recorded result for ${waiting.toolName}`));
|
|
163
|
+
waiting.resolve(answered.output);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
waiting.reject(error);
|
|
166
|
+
}
|
|
167
|
+
await finishLive(controller, state, adopted.drained);
|
|
168
|
+
};
|
|
169
|
+
const finishLive = async (controller, state, drained) => {
|
|
170
|
+
const outcome = await Promise.race([state.fxTurn.result.then((result) => ({
|
|
171
|
+
ended: true,
|
|
172
|
+
result
|
|
173
|
+
})), state.toolsRequested.promise.then(() => ({ ended: false }))]).catch((error) => {
|
|
174
|
+
throw state.failure ?? error;
|
|
175
|
+
});
|
|
176
|
+
if (state.failure) throw state.failure;
|
|
177
|
+
await state.parsed;
|
|
178
|
+
if (outcome.ended) {
|
|
179
|
+
if (outcome.result.stopReason === "cancelled" && !stopped && !step.signal.aborted) throw diverged("the turn was cancelled while this step ran it");
|
|
180
|
+
await drained;
|
|
181
|
+
if (outcome.result.stopReason === "end_turn" && !step.signal.aborted) await saveCheckpoint();
|
|
182
|
+
} else if (kernel.waiting.length > 0 && !stopped && !closed) {
|
|
183
|
+
lot.park(step.sessionId, {
|
|
184
|
+
kernel,
|
|
185
|
+
fxTurn: state.fxTurn,
|
|
186
|
+
drained,
|
|
187
|
+
generationId: step.generationId,
|
|
188
|
+
settings
|
|
189
|
+
});
|
|
190
|
+
parked = true;
|
|
191
|
+
} else state.fxTurn.cancel();
|
|
192
|
+
controller.enqueue({ type: "finish-step" });
|
|
193
|
+
controller.enqueue({
|
|
194
|
+
type: "finish",
|
|
195
|
+
...completion.finishReason === void 0 ? {} : { finishReason: completion.finishReason }
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Whether a parked kernel stands where this step's log does: made the same
|
|
200
|
+
* way, its model call the last one the log's live turn committed, and a
|
|
201
|
+
* recorded result for each tool call it waits on. A steered response
|
|
202
|
+
* continues the same turn, so its steps qualify too.
|
|
203
|
+
*/
|
|
204
|
+
const adoptable = (entry, turns) => {
|
|
205
|
+
const plan = turns.at(-1);
|
|
206
|
+
if (entry.settings !== settings || plan?.end !== "live" || plan.generationId !== entry.generationId) return false;
|
|
207
|
+
const unanswered = [...plan.calls.at(-1)?.toolCalls ?? []];
|
|
208
|
+
return entry.kernel.waiting.every((waiting) => {
|
|
209
|
+
const at = unanswered.findIndex((call) => call.toolName === waiting.toolName && sameJson(call.input, waiting.input) && results.has(call.toolCallId));
|
|
210
|
+
if (at === -1) return false;
|
|
211
|
+
unanswered.splice(at, 1);
|
|
212
|
+
return true;
|
|
213
|
+
});
|
|
214
|
+
};
|
|
98
215
|
const saveCheckpoint = async () => {
|
|
99
216
|
let checkpoint;
|
|
100
217
|
try {
|
|
101
|
-
checkpoint = await agent.checkpoint();
|
|
218
|
+
checkpoint = await kernel.agent.checkpoint();
|
|
102
219
|
} catch {
|
|
103
220
|
return;
|
|
104
221
|
}
|
|
@@ -119,7 +236,8 @@ async function runStep(step) {
|
|
|
119
236
|
const state = turn;
|
|
120
237
|
if (state.failure) throw state.failure;
|
|
121
238
|
const body = decodeBody(init.body);
|
|
122
|
-
const
|
|
239
|
+
const request = JSON.parse(body);
|
|
240
|
+
const compaction = request.toolChoice?.type === "none";
|
|
123
241
|
const recorded = state.plan.calls[state.next];
|
|
124
242
|
if (recorded !== void 0) {
|
|
125
243
|
if (recorded.kind === "compaction" !== compaction) fail(state, diverged(`request ${state.next} is not a ${recorded.kind}`));
|
|
@@ -145,7 +263,7 @@ async function runStep(step) {
|
|
|
145
263
|
state.live = true;
|
|
146
264
|
return live(state, url, {
|
|
147
265
|
...init,
|
|
148
|
-
body
|
|
266
|
+
body: withGatewayCaching(body, request)
|
|
149
267
|
}, compaction);
|
|
150
268
|
};
|
|
151
269
|
const affinity = gatewaySessionAffinity({
|
|
@@ -158,7 +276,6 @@ async function runStep(step) {
|
|
|
158
276
|
headers.set("x-session-affinity", affinity);
|
|
159
277
|
const response = await fetch(url, {
|
|
160
278
|
...init,
|
|
161
|
-
body: withGatewayCaching(init.body),
|
|
162
279
|
headers,
|
|
163
280
|
signal: network
|
|
164
281
|
});
|
|
@@ -202,42 +319,67 @@ async function runStep(step) {
|
|
|
202
319
|
headers: response.headers
|
|
203
320
|
});
|
|
204
321
|
};
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (state.failure) throw state.failure;
|
|
322
|
+
/** The recorded result of a call from the step the kernel was served. */
|
|
323
|
+
const answer = (state, toolName, toolInput) => {
|
|
208
324
|
const call = state.served.find((candidate) => candidate.toolName === toolName && !state.answered.has(candidate.toolCallId) && sameJson(candidate.input, toolInput));
|
|
209
325
|
const result = call === void 0 ? void 0 : results.get(call.toolCallId);
|
|
210
|
-
if (call
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
326
|
+
if (call === void 0 || result === void 0) return void 0;
|
|
327
|
+
state.answered.add(call.toolCallId);
|
|
328
|
+
if (result.error !== void 0) throw new Error(result.error);
|
|
329
|
+
if (result.denied === true) throw new Error("The tool call was denied.");
|
|
330
|
+
return { output: result.output ?? null };
|
|
331
|
+
};
|
|
332
|
+
const hostTool = (toolName, toolInput) => {
|
|
333
|
+
const state = turn;
|
|
334
|
+
if (state.failure) throw state.failure;
|
|
335
|
+
const answered = answer(state, toolName, toolInput);
|
|
336
|
+
if (answered !== void 0) return answered.output;
|
|
216
337
|
if (state.live) {
|
|
217
338
|
state.toolsRequested.resolve();
|
|
218
|
-
return
|
|
339
|
+
return new Promise((resolve, reject) => {
|
|
340
|
+
kernel.waiting.push({
|
|
341
|
+
toolName,
|
|
342
|
+
input: toolInput,
|
|
343
|
+
resolve,
|
|
344
|
+
reject
|
|
345
|
+
});
|
|
346
|
+
});
|
|
219
347
|
}
|
|
220
348
|
if (state.plan.end === "interrupted") {
|
|
221
349
|
state.fxTurn.cancel();
|
|
222
350
|
return pending(void 0);
|
|
223
351
|
}
|
|
224
352
|
return fail(state, diverged(`no recorded result for ${toolName}`));
|
|
225
|
-
}
|
|
226
|
-
const
|
|
353
|
+
};
|
|
354
|
+
const hooks = {
|
|
355
|
+
fetch: hostFetch,
|
|
356
|
+
tool: hostTool
|
|
357
|
+
};
|
|
358
|
+
const planTurns = (turns) => Promise.all(turns.map((recorded) => turnPlan(recorded, step.signal)));
|
|
227
359
|
let results = read.results;
|
|
228
360
|
let controller;
|
|
229
361
|
let closed = false;
|
|
230
362
|
const emit = (chunk) => {
|
|
231
363
|
if (!closed) controller.enqueue(chunk);
|
|
232
364
|
};
|
|
233
|
-
const create = (checkpoint) =>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
365
|
+
const create = async (checkpoint) => {
|
|
366
|
+
const created = {
|
|
367
|
+
hooks,
|
|
368
|
+
waiting: []
|
|
369
|
+
};
|
|
370
|
+
const agent = await createFxAgent({
|
|
371
|
+
apiKey: credential.token,
|
|
372
|
+
model: kernelModel(step),
|
|
373
|
+
...step.instructions.length === 0 ? {} : { instructions: step.instructions },
|
|
374
|
+
tools: definitions.map((definition) => ({
|
|
375
|
+
...definition,
|
|
376
|
+
execute: (toolInput) => created.hooks.tool(definition.name, toolInput)
|
|
377
|
+
})),
|
|
378
|
+
...checkpoint === void 0 ? {} : { checkpoint },
|
|
379
|
+
fetch: (input, init) => created.hooks.fetch(input, init)
|
|
380
|
+
});
|
|
381
|
+
return Object.assign(created, { agent });
|
|
382
|
+
};
|
|
241
383
|
return {
|
|
242
384
|
stream: new ReadableStream({
|
|
243
385
|
start(streamController) {
|
|
@@ -249,15 +391,23 @@ async function runStep(step) {
|
|
|
249
391
|
step.signal.addEventListener("abort", abort, { once: true });
|
|
250
392
|
(async () => {
|
|
251
393
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
394
|
+
let turns = await planTurns(read.turns);
|
|
395
|
+
const candidate = step.signal.aborted ? void 0 : lot.take(step.sessionId);
|
|
396
|
+
const adopted = candidate !== void 0 && adoptable(candidate, turns) ? candidate : void 0;
|
|
397
|
+
if (candidate !== void 0 && adopted === void 0) closeParked(candidate);
|
|
398
|
+
if (adopted !== void 0) {
|
|
399
|
+
kernel = adopted.kernel;
|
|
400
|
+
kernel.hooks = hooks;
|
|
401
|
+
} else try {
|
|
402
|
+
kernel = await create(read.saved);
|
|
254
403
|
} catch (error) {
|
|
255
404
|
if (read.saved === void 0) throw error;
|
|
256
405
|
read = await replayAll();
|
|
257
406
|
results = read.results;
|
|
258
|
-
|
|
407
|
+
kernel = await create(void 0);
|
|
408
|
+
turns = await planTurns(read.turns);
|
|
259
409
|
}
|
|
260
|
-
await run(streamController,
|
|
410
|
+
await run(streamController, turns, adopted);
|
|
261
411
|
closed = true;
|
|
262
412
|
streamController.close();
|
|
263
413
|
} catch (error) {
|
|
@@ -266,7 +416,7 @@ async function runStep(step) {
|
|
|
266
416
|
} finally {
|
|
267
417
|
released.abort();
|
|
268
418
|
step.signal.removeEventListener("abort", abort);
|
|
269
|
-
await agent
|
|
419
|
+
if (!parked) await kernel?.agent.close().catch(() => {});
|
|
270
420
|
}
|
|
271
421
|
})();
|
|
272
422
|
},
|
|
@@ -293,7 +443,9 @@ async function turnPlan(turn, signal) {
|
|
|
293
443
|
input: turn.prompt,
|
|
294
444
|
signal
|
|
295
445
|
});
|
|
446
|
+
const generationId = turn.generations.at(-1)?.completed.generationId;
|
|
296
447
|
return {
|
|
448
|
+
...generationId === void 0 ? {} : { generationId },
|
|
297
449
|
prompt: images.length === 0 ? turn.prompt.text : [...images.map((image) => ({
|
|
298
450
|
type: "image",
|
|
299
451
|
data: image.data,
|
|
@@ -602,8 +754,8 @@ async function loadLibfx() {
|
|
|
602
754
|
* asks for it; options the request already has take precedence. Every other
|
|
603
755
|
* byte stays as the kernel wrote it, a tool input's spacing included.
|
|
604
756
|
*/
|
|
605
|
-
function withGatewayCaching(body) {
|
|
606
|
-
const options =
|
|
757
|
+
function withGatewayCaching(body, request) {
|
|
758
|
+
const options = request.providerOptions;
|
|
607
759
|
if (options === void 0) return body.replace(/^\{/, "{\"providerOptions\":{\"gateway\":{\"caching\":\"auto\"}},");
|
|
608
760
|
const own = `"providerOptions":${JSON.stringify(options)}}`;
|
|
609
761
|
if (!body.endsWith(own)) return body;
|
|
@@ -619,6 +771,7 @@ function withGatewayCaching(body) {
|
|
|
619
771
|
function withAuth(headers, credential) {
|
|
620
772
|
const result = new Headers(headers);
|
|
621
773
|
result.set("ai-gateway-auth-method", credential.method);
|
|
774
|
+
if (result.has("authorization")) result.set("authorization", `Bearer ${credential.token}`);
|
|
622
775
|
return result;
|
|
623
776
|
}
|
|
624
777
|
function kernelModel(step) {
|
|
@@ -631,12 +784,12 @@ function kernelModel(step) {
|
|
|
631
784
|
...fast ? { fast } : {}
|
|
632
785
|
};
|
|
633
786
|
}
|
|
634
|
-
|
|
787
|
+
/** The agent's tools as the kernel takes them, without their execution. */
|
|
788
|
+
async function toolDefinitions(tools) {
|
|
635
789
|
return Promise.all(Object.entries(tools).map(async ([toolName, tool]) => ({
|
|
636
790
|
name: toolName,
|
|
637
791
|
description: typeof tool.description === "string" ? tool.description : toolName,
|
|
638
|
-
inputSchema: await asSchema(tool.inputSchema).jsonSchema
|
|
639
|
-
execute: (input) => execute(toolName, input)
|
|
792
|
+
inputSchema: await asSchema(tool.inputSchema).jsonSchema
|
|
640
793
|
})));
|
|
641
794
|
}
|
|
642
795
|
const catalogCache = boundedCache({
|
|
@@ -671,9 +824,6 @@ function savedCheckpoint(state) {
|
|
|
671
824
|
if (version !== SNAPSHOT_VERSION || typeof checkpoint !== "string") return void 0;
|
|
672
825
|
return new Uint8Array(Buffer.from(checkpoint, "base64"));
|
|
673
826
|
}
|
|
674
|
-
function isCompaction(body) {
|
|
675
|
-
return JSON.parse(body).toolChoice?.type === "none";
|
|
676
|
-
}
|
|
677
827
|
function decodeBody(body) {
|
|
678
828
|
if (typeof body === "string") return body;
|
|
679
829
|
if (body instanceof Uint8Array || body instanceof ArrayBuffer) return new TextDecoder().decode(body);
|