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.
Files changed (139) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/{actor-HnGmrOZZ.d.ts → actor-CqibLgjQ.d.ts} +13 -5
  3. package/dist/actor-CqibLgjQ.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +1 -1
  5. package/dist/actor-react.d.ts +1 -1
  6. package/dist/actor-react.js +1 -1
  7. package/dist/actor-shared-DFET5q16.js.map +1 -1
  8. package/dist/actor.d.ts +1 -1
  9. package/dist/actor.js +3 -3
  10. package/dist/{ai-CvEnqVfo.d.ts → ai-DAu77CDr.d.ts} +2 -2
  11. package/dist/{ai-CvEnqVfo.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
  12. package/dist/{ai-CzE672Q3.js → ai-EEfKyc-z.js} +2 -2
  13. package/dist/{ai-CzE672Q3.js.map → ai-EEfKyc-z.js.map} +1 -1
  14. package/dist/ai-client.d.ts +1 -1
  15. package/dist/ai-client.js +2 -2
  16. package/dist/ai-fx.d.ts +4 -2
  17. package/dist/ai-fx.d.ts.map +1 -1
  18. package/dist/ai-fx.js +211 -61
  19. package/dist/ai-fx.js.map +1 -1
  20. package/dist/{ai-harness-Bfk-Lycn.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
  21. package/dist/{ai-harness-Bfk-Lycn.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
  22. package/dist/ai-harness-dQeJaR1t.js.map +1 -1
  23. package/dist/ai-pi.d.ts +1 -1
  24. package/dist/ai-pi.js +1 -1
  25. package/dist/ai-react.d.ts +1 -1
  26. package/dist/ai-react.js +2 -2
  27. package/dist/ai-server.d.ts +3 -3
  28. package/dist/ai-server.d.ts.map +1 -1
  29. package/dist/ai-server.js +10 -4
  30. package/dist/ai-server.js.map +1 -1
  31. package/dist/ai.d.ts +1 -1
  32. package/dist/ai.js +1 -1
  33. package/dist/{client-9IeTsiAs.js → client-BQS4i_V9.js} +27 -4
  34. package/dist/client-BQS4i_V9.js.map +1 -0
  35. package/dist/client-CUMbS_38.d.ts.map +1 -1
  36. package/dist/client.js +1 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/{internal-Bc4PvfHk.js → internal-CvsrprnU.js} +32 -15
  39. package/dist/{internal-Bc4PvfHk.js.map → internal-CvsrprnU.js.map} +1 -1
  40. package/dist/react.js +1 -1
  41. package/dist/{runtime-consumer-BWmht60z.js → runtime-consumer-CIeL33wj.js} +2 -2
  42. package/dist/{runtime-consumer-BWmht60z.js.map → runtime-consumer-CIeL33wj.js.map} +1 -1
  43. package/dist/scheduler-qstash.d.ts +2 -2
  44. package/dist/scheduler-qstash.js +2 -2
  45. package/dist/scheduler-vercel.d.ts +2 -2
  46. package/dist/scheduler-vercel.js +2 -2
  47. package/dist/{server-DvIB8kj5.js → server-DtFrWxi3.js} +30 -8
  48. package/dist/server-DtFrWxi3.js.map +1 -0
  49. package/dist/{server-BshFZ0eL.d.ts → server-qgez0Xny.d.ts} +3 -3
  50. package/dist/{server-BshFZ0eL.d.ts.map → server-qgez0Xny.d.ts.map} +1 -1
  51. package/dist/server.d.ts +2 -2
  52. package/dist/server.js +1 -1
  53. package/dist/{session-clear-Bk_29eEw.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
  54. package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
  55. package/dist/{store-Z2qjw50n.d.ts → store-D0MhBvg6.d.ts} +15 -6
  56. package/dist/store-D0MhBvg6.d.ts.map +1 -0
  57. package/dist/store-N8PXxDAS.js.map +1 -1
  58. package/dist/store-memory.d.ts +1 -1
  59. package/dist/store-memory.d.ts.map +1 -1
  60. package/dist/store-memory.js +15 -17
  61. package/dist/store-memory.js.map +1 -1
  62. package/dist/{store-polling-DYNyxCev.js → store-polling-BZt2Rf3D.js} +2 -2
  63. package/dist/{store-polling-DYNyxCev.js.map → store-polling-BZt2Rf3D.js.map} +1 -1
  64. package/dist/store-postgres.d.ts +1 -1
  65. package/dist/store-postgres.d.ts.map +1 -1
  66. package/dist/store-postgres.js +31 -40
  67. package/dist/store-postgres.js.map +1 -1
  68. package/dist/store-presence-polling-JuAy2Ck2.js +111 -0
  69. package/dist/store-presence-polling-JuAy2Ck2.js.map +1 -0
  70. package/dist/store-redis-http.d.ts +1 -1
  71. package/dist/store-redis-http.js +3 -3
  72. package/dist/{store-redis-notify-IiVdlYHc.js → store-redis-notify-CsJw_hfD.js} +538 -397
  73. package/dist/store-redis-notify-CsJw_hfD.js.map +1 -0
  74. package/dist/store-redis.d.ts +1 -1
  75. package/dist/store-redis.d.ts.map +1 -1
  76. package/dist/store-redis.js +3 -4
  77. package/dist/store-redis.js.map +1 -1
  78. package/dist/store-sqlite.d.ts +1 -1
  79. package/dist/store-sqlite.d.ts.map +1 -1
  80. package/dist/store-sqlite.js +16 -18
  81. package/dist/store-sqlite.js.map +1 -1
  82. package/dist/stream-buffer-DrTyiIuo.js +57 -0
  83. package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
  84. package/dist/{wire-CZGqNZRx.js → wire-CV-Z7wz-.js} +3 -3
  85. package/dist/{wire-CZGqNZRx.js.map → wire-CV-Z7wz-.js.map} +1 -1
  86. package/docs/actors/01-introduction.mdx +5 -2
  87. package/docs/agents/03-conversations.mdx +9 -8
  88. package/docs/agents/04-harnesses.mdx +7 -4
  89. package/docs/reference/01-core.mdx +3 -2
  90. package/docs/reference/02-server.mdx +5 -1
  91. package/docs/reference/04-ai.mdx +13 -4
  92. package/docs/reference/06-errors.mdx +1 -1
  93. package/docs/server/01-consumers.mdx +4 -1
  94. package/docs/server/06-guarantees.mdx +7 -4
  95. package/docs/stores/02-redis.mdx +16 -10
  96. package/docs/stores/03-redis-http.mdx +6 -4
  97. package/docs/stores/04-postgres.mdx +2 -2
  98. package/docs/upgrades/01-overview.mdx +1 -0
  99. package/docs/upgrades/78-0-22.mdx +115 -0
  100. package/examples/playground/app/a0/sandbox.ts +58 -13
  101. package/examples/playground/app/a2slack/db/a2.ts +7 -4
  102. package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
  103. package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
  104. package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +8 -1
  105. package/examples/playground/app/chat/server.ts +2 -2
  106. package/examples/playground/lib/store.ts +1 -1
  107. package/examples/playground/package.json +1 -1
  108. package/package.json +1 -1
  109. package/sql/postgres/drizzle-schema.ts +7 -4
  110. package/sql/postgres/schema.sql +24 -3
  111. package/src/actor-shared.ts +10 -2
  112. package/src/ai-agent.ts +13 -0
  113. package/src/ai-fx.ts +321 -77
  114. package/src/ai-harness.ts +7 -0
  115. package/src/client.ts +19 -2
  116. package/src/deterministic-id.ts +1 -1
  117. package/src/internal.ts +42 -11
  118. package/src/presence-recovery.ts +18 -11
  119. package/src/presence-tombstones.ts +52 -0
  120. package/src/push-envelope.ts +2 -2
  121. package/src/server.ts +49 -15
  122. package/src/store-memory.ts +24 -32
  123. package/src/store-postgres.ts +43 -53
  124. package/src/store-presence-polling.ts +59 -34
  125. package/src/store-redis-core.ts +595 -439
  126. package/src/store-redis-notify.ts +74 -26
  127. package/src/store-redis.ts +2 -3
  128. package/src/store-sqlite.ts +34 -40
  129. package/src/store.ts +14 -5
  130. package/dist/actor-HnGmrOZZ.d.ts.map +0 -1
  131. package/dist/client-9IeTsiAs.js.map +0 -1
  132. package/dist/server-DvIB8kj5.js.map +0 -1
  133. package/dist/session-clear-Bk_29eEw.d.ts.map +0 -1
  134. package/dist/store-Z2qjw50n.d.ts.map +0 -1
  135. package/dist/store-presence-polling-CzQ0wdtM.js +0 -94
  136. package/dist/store-presence-polling-CzQ0wdtM.js.map +0 -1
  137. package/dist/store-redis-notify-IiVdlYHc.js.map +0 -1
  138. package/dist/stream-buffer-Cx7AAPGq.js +0 -23
  139. package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
@@ -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-CvEnqVfo.js";
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-9IeTsiAs.js";
2
- import { n as agentContract } from "./ai-CzE672Q3.js";
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-Bfk-Lycn.js";
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
@@ -1 +1 @@
1
- {"version":3,"file":"ai-fx.d.ts","names":[],"sources":["../src/ai-fx.ts"],"mappings":";;;;;;iBA4CgB,MAAM"}
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
- async function runStep(step) {
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 agent;
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, { signal: step.signal });
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
- const outcome = await Promise.race([fxTurn.result.then((result) => ({
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 compaction = isCompaction(body);
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
- const hostTools = await kernelTools(step.tools, (toolName, toolInput) => {
206
- const state = turn;
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 !== void 0 && result !== void 0) {
211
- state.answered.add(call.toolCallId);
212
- if (result.error !== void 0) throw new Error(result.error);
213
- if (result.denied === true) throw new Error("The tool call was denied.");
214
- return result.output ?? null;
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 pending(void 0);
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 plans = (turns) => Promise.all(turns.map((recorded) => turnPlan(recorded, step.signal)));
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) => createFxAgent({
234
- apiKey: credential.token,
235
- model: kernelModel(step),
236
- ...step.instructions.length === 0 ? {} : { instructions: step.instructions },
237
- tools: hostTools,
238
- ...checkpoint === void 0 ? {} : { checkpoint },
239
- fetch: hostFetch
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
- try {
253
- agent = await create(read.saved);
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
- agent = await create(void 0);
407
+ kernel = await create(void 0);
408
+ turns = await planTurns(read.turns);
259
409
  }
260
- await run(streamController, await plans(read.turns));
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?.close().catch(() => {});
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 = JSON.parse(body).providerOptions;
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
- async function kernelTools(tools, execute) {
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);