experimental-a2 0.21.1 → 0.23.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 +32 -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 +234 -62
- 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-CBDVJxA_.js} +545 -399
- package/dist/store-redis-notify-CBDVJxA_.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/store-valkey.d.ts +33 -0
- package/dist/store-valkey.d.ts.map +1 -0
- package/dist/store-valkey.js +218 -0
- package/dist/store-valkey.js.map +1 -0
- 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/client/03-presence.mdx +1 -0
- package/docs/reference/01-core.mdx +4 -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/01-overview.mdx +1 -0
- package/docs/stores/02-redis.mdx +18 -10
- package/docs/stores/03-redis-http.mdx +6 -4
- package/docs/stores/04-postgres.mdx +2 -2
- package/docs/stores/07-valkey.mdx +97 -0
- 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 +8 -2
- 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 +365 -78
- 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/libfx.d.ts +2 -0
- 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 +82 -28
- package/src/store-redis.ts +2 -3
- package/src/store-sqlite.ts +34 -40
- package/src/store-valkey.ts +376 -0
- 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,28 +10,87 @@ 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;
|
|
21
37
|
const CATALOG_TTL_MS = 36e5;
|
|
22
38
|
/** The Gateway model catalog libfx reads for each new kernel. */
|
|
23
39
|
const CATALOG_PATH = "/coding-agent/v1/models";
|
|
40
|
+
/** Times a step rebuilds its kernel for a stopped turn FX failed on replay. */
|
|
41
|
+
const REPLAY_REBUILDS = 3;
|
|
24
42
|
function fail(state, error) {
|
|
25
43
|
state.failure ??= error;
|
|
26
44
|
state.fxTurn.cancel();
|
|
27
45
|
throw state.failure;
|
|
28
46
|
}
|
|
29
47
|
const diverged = (message) => new Divergence("FX", message);
|
|
30
|
-
|
|
48
|
+
function closeParked(entry) {
|
|
49
|
+
entry.fxTurn.cancel();
|
|
50
|
+
entry.kernel.agent.close().catch(() => {});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* One agent's parked kernels: one per session, at most `maxKernels`. Each is
|
|
54
|
+
* closed when its session has nothing left to run in this process (the
|
|
55
|
+
* agent's release), when its turn ends, or when another takes its place.
|
|
56
|
+
*/
|
|
57
|
+
function parkingLot() {
|
|
58
|
+
const held = /* @__PURE__ */ new Map();
|
|
59
|
+
const take = (sessionId) => {
|
|
60
|
+
const entry = held.get(sessionId);
|
|
61
|
+
held.delete(sessionId);
|
|
62
|
+
return entry;
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
take,
|
|
66
|
+
park(sessionId, entry) {
|
|
67
|
+
const replaced = take(sessionId);
|
|
68
|
+
if (replaced !== void 0) closeParked(replaced);
|
|
69
|
+
held.set(sessionId, entry);
|
|
70
|
+
const ended = () => {
|
|
71
|
+
if (held.get(sessionId) !== entry) return;
|
|
72
|
+
held.delete(sessionId);
|
|
73
|
+
closeParked(entry);
|
|
74
|
+
};
|
|
75
|
+
entry.fxTurn.result.then(ended, ended);
|
|
76
|
+
const oldest = held.keys().next().value;
|
|
77
|
+
if (held.size > HARNESS_PARKING.maxKernels && oldest !== void 0) {
|
|
78
|
+
const evicted = take(oldest);
|
|
79
|
+
if (evicted !== void 0) closeParked(evicted);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function runStep(step, lot) {
|
|
31
85
|
const { createFxAgent } = await loadLibfx();
|
|
32
86
|
const credential = gatewayCredential("fx");
|
|
33
87
|
const name = `${step.agentName}.a2.harness.fx`;
|
|
88
|
+
const definitions = await toolDefinitions(step.tools);
|
|
89
|
+
const settings = JSON.stringify([
|
|
90
|
+
kernelModel(step),
|
|
91
|
+
step.instructions,
|
|
92
|
+
definitions
|
|
93
|
+
]);
|
|
34
94
|
const { replayAll, ...first } = await readHarness({
|
|
35
95
|
step,
|
|
36
96
|
name,
|
|
@@ -41,16 +101,26 @@ async function runStep(step) {
|
|
|
41
101
|
let stopped = false;
|
|
42
102
|
const released = new AbortController();
|
|
43
103
|
const network = AbortSignal.any([step.signal, released.signal]);
|
|
44
|
-
let
|
|
104
|
+
let kernel;
|
|
105
|
+
/** Whether this step left its kernel for the next one. */
|
|
106
|
+
let parked = false;
|
|
45
107
|
let turn;
|
|
46
|
-
const run = async (controller, turns) => {
|
|
108
|
+
const run = async (controller, turns, adopted) => {
|
|
47
109
|
controller.enqueue({
|
|
48
110
|
type: "start",
|
|
49
111
|
messageId: step.responseMessageId
|
|
50
112
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
113
|
+
if (adopted !== void 0) {
|
|
114
|
+
await resume(controller, turns.at(-1), adopted);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
let rebuilds = 0;
|
|
118
|
+
let next = 0;
|
|
119
|
+
while (next < turns.length) {
|
|
120
|
+
if (stopped || step.signal.aborted) return;
|
|
121
|
+
const plan = turns[next];
|
|
122
|
+
next += 1;
|
|
123
|
+
const fxTurn = kernel.agent.prompt(plan.prompt);
|
|
54
124
|
const state = {
|
|
55
125
|
plan,
|
|
56
126
|
fxTurn,
|
|
@@ -74,31 +144,91 @@ async function runStep(step) {
|
|
|
74
144
|
if (state.failure) throw state.failure;
|
|
75
145
|
const expected = plan.end === "stop" ? "end_turn" : "cancelled";
|
|
76
146
|
if (result.stopReason !== expected) throw diverged(`a recorded turn ended with ${result.stopReason}, not ${expected}`);
|
|
147
|
+
if (kernel.failed) {
|
|
148
|
+
if (rebuilds === REPLAY_REBUILDS) throw diverged("FX failed a stopped turn instead of cancelling it");
|
|
149
|
+
rebuilds += 1;
|
|
150
|
+
await kernel.agent.close().catch(() => {});
|
|
151
|
+
kernel = await create(read.saved);
|
|
152
|
+
next = 0;
|
|
153
|
+
}
|
|
77
154
|
continue;
|
|
78
155
|
}
|
|
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
|
-
});
|
|
156
|
+
await finishLive(controller, state, drained);
|
|
96
157
|
}
|
|
97
158
|
};
|
|
159
|
+
/** Continues a parked kernel: the results it waits on are in the log. */
|
|
160
|
+
const resume = async (controller, plan, adopted) => {
|
|
161
|
+
const state = {
|
|
162
|
+
plan,
|
|
163
|
+
fxTurn: adopted.fxTurn,
|
|
164
|
+
next: plan.calls.length,
|
|
165
|
+
served: plan.calls.at(-1).toolCalls,
|
|
166
|
+
answered: /* @__PURE__ */ new Set(),
|
|
167
|
+
live: false,
|
|
168
|
+
liveStep: false,
|
|
169
|
+
toolsRequested: deferred()
|
|
170
|
+
};
|
|
171
|
+
turn = state;
|
|
172
|
+
for (const text of plan.steers.get(plan.calls.length - 1) ?? []) await adopted.fxTurn.steer(text);
|
|
173
|
+
for (const waiting of kernel.waiting.splice(0)) try {
|
|
174
|
+
const answered = answer(state, waiting.toolName, waiting.input);
|
|
175
|
+
if (answered === void 0) fail(state, diverged(`no recorded result for ${waiting.toolName}`));
|
|
176
|
+
waiting.resolve(answered.output);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
waiting.reject(error);
|
|
179
|
+
}
|
|
180
|
+
await finishLive(controller, state, adopted.drained);
|
|
181
|
+
};
|
|
182
|
+
const finishLive = async (controller, state, drained) => {
|
|
183
|
+
const outcome = await Promise.race([state.fxTurn.result.then((result) => ({
|
|
184
|
+
ended: true,
|
|
185
|
+
result
|
|
186
|
+
})), state.toolsRequested.promise.then(() => ({ ended: false }))]).catch((error) => {
|
|
187
|
+
throw state.failure ?? error;
|
|
188
|
+
});
|
|
189
|
+
if (state.failure) throw state.failure;
|
|
190
|
+
await state.parsed;
|
|
191
|
+
if (outcome.ended) {
|
|
192
|
+
if (outcome.result.stopReason === "cancelled" && !stopped && !step.signal.aborted) throw diverged("the turn was cancelled while this step ran it");
|
|
193
|
+
await drained;
|
|
194
|
+
if (outcome.result.stopReason === "end_turn" && !step.signal.aborted) await saveCheckpoint();
|
|
195
|
+
} else if (kernel.waiting.length > 0 && !stopped && !closed) {
|
|
196
|
+
lot.park(step.sessionId, {
|
|
197
|
+
kernel,
|
|
198
|
+
fxTurn: state.fxTurn,
|
|
199
|
+
drained,
|
|
200
|
+
generationId: step.generationId,
|
|
201
|
+
settings
|
|
202
|
+
});
|
|
203
|
+
parked = true;
|
|
204
|
+
} else state.fxTurn.cancel();
|
|
205
|
+
controller.enqueue({ type: "finish-step" });
|
|
206
|
+
controller.enqueue({
|
|
207
|
+
type: "finish",
|
|
208
|
+
...completion.finishReason === void 0 ? {} : { finishReason: completion.finishReason }
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Whether a parked kernel stands where this step's log does: made the same
|
|
213
|
+
* way, its model call the last one the log's live turn committed, and a
|
|
214
|
+
* recorded result for each tool call it waits on. A steered response
|
|
215
|
+
* continues the same turn, so its steps qualify too.
|
|
216
|
+
*/
|
|
217
|
+
const adoptable = (entry, turns) => {
|
|
218
|
+
const plan = turns.at(-1);
|
|
219
|
+
if (entry.settings !== settings || plan?.end !== "live" || plan.generationId !== entry.generationId) return false;
|
|
220
|
+
const unanswered = [...plan.calls.at(-1)?.toolCalls ?? []];
|
|
221
|
+
return entry.kernel.waiting.every((waiting) => {
|
|
222
|
+
const at = unanswered.findIndex((call) => call.toolName === waiting.toolName && sameJson(call.input, waiting.input) && results.has(call.toolCallId));
|
|
223
|
+
if (at === -1) return false;
|
|
224
|
+
unanswered.splice(at, 1);
|
|
225
|
+
return true;
|
|
226
|
+
});
|
|
227
|
+
};
|
|
98
228
|
const saveCheckpoint = async () => {
|
|
99
229
|
let checkpoint;
|
|
100
230
|
try {
|
|
101
|
-
checkpoint = await agent.checkpoint();
|
|
231
|
+
checkpoint = await kernel.agent.checkpoint();
|
|
102
232
|
} catch {
|
|
103
233
|
return;
|
|
104
234
|
}
|
|
@@ -119,7 +249,8 @@ async function runStep(step) {
|
|
|
119
249
|
const state = turn;
|
|
120
250
|
if (state.failure) throw state.failure;
|
|
121
251
|
const body = decodeBody(init.body);
|
|
122
|
-
const
|
|
252
|
+
const request = JSON.parse(body);
|
|
253
|
+
const compaction = request.toolChoice?.type === "none";
|
|
123
254
|
const recorded = state.plan.calls[state.next];
|
|
124
255
|
if (recorded !== void 0) {
|
|
125
256
|
if (recorded.kind === "compaction" !== compaction) fail(state, diverged(`request ${state.next} is not a ${recorded.kind}`));
|
|
@@ -145,7 +276,7 @@ async function runStep(step) {
|
|
|
145
276
|
state.live = true;
|
|
146
277
|
return live(state, url, {
|
|
147
278
|
...init,
|
|
148
|
-
body
|
|
279
|
+
body: withGatewayCaching(body, request)
|
|
149
280
|
}, compaction);
|
|
150
281
|
};
|
|
151
282
|
const affinity = gatewaySessionAffinity({
|
|
@@ -158,7 +289,6 @@ async function runStep(step) {
|
|
|
158
289
|
headers.set("x-session-affinity", affinity);
|
|
159
290
|
const response = await fetch(url, {
|
|
160
291
|
...init,
|
|
161
|
-
body: withGatewayCaching(init.body),
|
|
162
292
|
headers,
|
|
163
293
|
signal: network
|
|
164
294
|
});
|
|
@@ -202,42 +332,76 @@ async function runStep(step) {
|
|
|
202
332
|
headers: response.headers
|
|
203
333
|
});
|
|
204
334
|
};
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (state.failure) throw state.failure;
|
|
335
|
+
/** The recorded result of a call from the step the kernel was served. */
|
|
336
|
+
const answer = (state, toolName, toolInput) => {
|
|
208
337
|
const call = state.served.find((candidate) => candidate.toolName === toolName && !state.answered.has(candidate.toolCallId) && sameJson(candidate.input, toolInput));
|
|
209
338
|
const result = call === void 0 ? void 0 : results.get(call.toolCallId);
|
|
210
|
-
if (call
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
339
|
+
if (call === void 0 || result === void 0) return void 0;
|
|
340
|
+
state.answered.add(call.toolCallId);
|
|
341
|
+
if (result.error !== void 0) throw new Error(result.error);
|
|
342
|
+
if (result.denied === true) throw new Error("The tool call was denied.");
|
|
343
|
+
return { output: result.output ?? null };
|
|
344
|
+
};
|
|
345
|
+
const hostTool = (toolName, toolInput) => {
|
|
346
|
+
const state = turn;
|
|
347
|
+
if (state.failure) throw state.failure;
|
|
348
|
+
const answered = answer(state, toolName, toolInput);
|
|
349
|
+
if (answered !== void 0) return answered.output;
|
|
216
350
|
if (state.live) {
|
|
217
351
|
state.toolsRequested.resolve();
|
|
218
|
-
return
|
|
352
|
+
return new Promise((resolve, reject) => {
|
|
353
|
+
kernel.waiting.push({
|
|
354
|
+
toolName,
|
|
355
|
+
input: toolInput,
|
|
356
|
+
resolve,
|
|
357
|
+
reject
|
|
358
|
+
});
|
|
359
|
+
});
|
|
219
360
|
}
|
|
220
361
|
if (state.plan.end === "interrupted") {
|
|
221
362
|
state.fxTurn.cancel();
|
|
222
363
|
return pending(void 0);
|
|
223
364
|
}
|
|
224
365
|
return fail(state, diverged(`no recorded result for ${toolName}`));
|
|
225
|
-
}
|
|
226
|
-
const
|
|
366
|
+
};
|
|
367
|
+
const hooks = {
|
|
368
|
+
fetch: hostFetch,
|
|
369
|
+
tool: hostTool
|
|
370
|
+
};
|
|
371
|
+
const planTurns = (turns) => Promise.all(turns.map((recorded) => turnPlan(recorded, step.signal)));
|
|
227
372
|
let results = read.results;
|
|
228
373
|
let controller;
|
|
229
374
|
let closed = false;
|
|
230
375
|
const emit = (chunk) => {
|
|
231
376
|
if (!closed) controller.enqueue(chunk);
|
|
232
377
|
};
|
|
233
|
-
const create = (checkpoint) =>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
378
|
+
const create = async (checkpoint) => {
|
|
379
|
+
const created = {
|
|
380
|
+
hooks,
|
|
381
|
+
waiting: [],
|
|
382
|
+
failed: false
|
|
383
|
+
};
|
|
384
|
+
let prompt;
|
|
385
|
+
const agent = await createFxAgent({
|
|
386
|
+
apiKey: credential.token,
|
|
387
|
+
model: kernelModel(step),
|
|
388
|
+
...step.instructions.length === 0 ? {} : { instructions: step.instructions },
|
|
389
|
+
tools: definitions.map((definition) => ({
|
|
390
|
+
...definition,
|
|
391
|
+
execute: (toolInput) => created.hooks.tool(definition.name, toolInput)
|
|
392
|
+
})),
|
|
393
|
+
...checkpoint === void 0 ? {} : { checkpoint },
|
|
394
|
+
fetch: (input, init) => created.hooks.fetch(input, init),
|
|
395
|
+
onEvent: ({ type, message }) => {
|
|
396
|
+
const rpc = message ?? {};
|
|
397
|
+
if (type === "acp.send" && rpc.method === "session/prompt") {
|
|
398
|
+
prompt = rpc.id;
|
|
399
|
+
created.failed = false;
|
|
400
|
+
} else if (type === "acp.receive" && rpc.method === void 0 && rpc.id === prompt) created.failed = rpc.error !== void 0;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
return Object.assign(created, { agent });
|
|
404
|
+
};
|
|
241
405
|
return {
|
|
242
406
|
stream: new ReadableStream({
|
|
243
407
|
start(streamController) {
|
|
@@ -249,15 +413,23 @@ async function runStep(step) {
|
|
|
249
413
|
step.signal.addEventListener("abort", abort, { once: true });
|
|
250
414
|
(async () => {
|
|
251
415
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
416
|
+
let turns = await planTurns(read.turns);
|
|
417
|
+
const candidate = step.signal.aborted ? void 0 : lot.take(step.sessionId);
|
|
418
|
+
const adopted = candidate !== void 0 && adoptable(candidate, turns) ? candidate : void 0;
|
|
419
|
+
if (candidate !== void 0 && adopted === void 0) closeParked(candidate);
|
|
420
|
+
if (adopted !== void 0) {
|
|
421
|
+
kernel = adopted.kernel;
|
|
422
|
+
kernel.hooks = hooks;
|
|
423
|
+
} else try {
|
|
424
|
+
kernel = await create(read.saved);
|
|
254
425
|
} catch (error) {
|
|
255
426
|
if (read.saved === void 0) throw error;
|
|
256
427
|
read = await replayAll();
|
|
257
428
|
results = read.results;
|
|
258
|
-
|
|
429
|
+
kernel = await create(void 0);
|
|
430
|
+
turns = await planTurns(read.turns);
|
|
259
431
|
}
|
|
260
|
-
await run(streamController,
|
|
432
|
+
await run(streamController, turns, adopted);
|
|
261
433
|
closed = true;
|
|
262
434
|
streamController.close();
|
|
263
435
|
} catch (error) {
|
|
@@ -266,7 +438,7 @@ async function runStep(step) {
|
|
|
266
438
|
} finally {
|
|
267
439
|
released.abort();
|
|
268
440
|
step.signal.removeEventListener("abort", abort);
|
|
269
|
-
await agent
|
|
441
|
+
if (!parked) await kernel?.agent.close().catch(() => {});
|
|
270
442
|
}
|
|
271
443
|
})();
|
|
272
444
|
},
|
|
@@ -293,7 +465,9 @@ async function turnPlan(turn, signal) {
|
|
|
293
465
|
input: turn.prompt,
|
|
294
466
|
signal
|
|
295
467
|
});
|
|
468
|
+
const generationId = turn.generations.at(-1)?.completed.generationId;
|
|
296
469
|
return {
|
|
470
|
+
...generationId === void 0 ? {} : { generationId },
|
|
297
471
|
prompt: images.length === 0 ? turn.prompt.text : [...images.map((image) => ({
|
|
298
472
|
type: "image",
|
|
299
473
|
data: image.data,
|
|
@@ -602,8 +776,8 @@ async function loadLibfx() {
|
|
|
602
776
|
* asks for it; options the request already has take precedence. Every other
|
|
603
777
|
* byte stays as the kernel wrote it, a tool input's spacing included.
|
|
604
778
|
*/
|
|
605
|
-
function withGatewayCaching(body) {
|
|
606
|
-
const options =
|
|
779
|
+
function withGatewayCaching(body, request) {
|
|
780
|
+
const options = request.providerOptions;
|
|
607
781
|
if (options === void 0) return body.replace(/^\{/, "{\"providerOptions\":{\"gateway\":{\"caching\":\"auto\"}},");
|
|
608
782
|
const own = `"providerOptions":${JSON.stringify(options)}}`;
|
|
609
783
|
if (!body.endsWith(own)) return body;
|
|
@@ -619,6 +793,7 @@ function withGatewayCaching(body) {
|
|
|
619
793
|
function withAuth(headers, credential) {
|
|
620
794
|
const result = new Headers(headers);
|
|
621
795
|
result.set("ai-gateway-auth-method", credential.method);
|
|
796
|
+
if (result.has("authorization")) result.set("authorization", `Bearer ${credential.token}`);
|
|
622
797
|
return result;
|
|
623
798
|
}
|
|
624
799
|
function kernelModel(step) {
|
|
@@ -631,12 +806,12 @@ function kernelModel(step) {
|
|
|
631
806
|
...fast ? { fast } : {}
|
|
632
807
|
};
|
|
633
808
|
}
|
|
634
|
-
|
|
809
|
+
/** The agent's tools as the kernel takes them, without their execution. */
|
|
810
|
+
async function toolDefinitions(tools) {
|
|
635
811
|
return Promise.all(Object.entries(tools).map(async ([toolName, tool]) => ({
|
|
636
812
|
name: toolName,
|
|
637
813
|
description: typeof tool.description === "string" ? tool.description : toolName,
|
|
638
|
-
inputSchema: await asSchema(tool.inputSchema).jsonSchema
|
|
639
|
-
execute: (input) => execute(toolName, input)
|
|
814
|
+
inputSchema: await asSchema(tool.inputSchema).jsonSchema
|
|
640
815
|
})));
|
|
641
816
|
}
|
|
642
817
|
const catalogCache = boundedCache({
|
|
@@ -671,9 +846,6 @@ function savedCheckpoint(state) {
|
|
|
671
846
|
if (version !== SNAPSHOT_VERSION || typeof checkpoint !== "string") return void 0;
|
|
672
847
|
return new Uint8Array(Buffer.from(checkpoint, "base64"));
|
|
673
848
|
}
|
|
674
|
-
function isCompaction(body) {
|
|
675
|
-
return JSON.parse(body).toolChoice?.type === "none";
|
|
676
|
-
}
|
|
677
849
|
function decodeBody(body) {
|
|
678
850
|
if (typeof body === "string") return body;
|
|
679
851
|
if (body instanceof Uint8Array || body instanceof ArrayBuffer) return new TextDecoder().decode(body);
|