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.
Files changed (148) hide show
  1. package/CHANGELOG.md +32 -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 +234 -62
  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-CBDVJxA_.js} +545 -399
  73. package/dist/store-redis-notify-CBDVJxA_.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/store-valkey.d.ts +33 -0
  83. package/dist/store-valkey.d.ts.map +1 -0
  84. package/dist/store-valkey.js +218 -0
  85. package/dist/store-valkey.js.map +1 -0
  86. package/dist/stream-buffer-DrTyiIuo.js +57 -0
  87. package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
  88. package/dist/{wire-CZGqNZRx.js → wire-CV-Z7wz-.js} +3 -3
  89. package/dist/{wire-CZGqNZRx.js.map → wire-CV-Z7wz-.js.map} +1 -1
  90. package/docs/actors/01-introduction.mdx +5 -2
  91. package/docs/agents/03-conversations.mdx +9 -8
  92. package/docs/agents/04-harnesses.mdx +7 -4
  93. package/docs/client/03-presence.mdx +1 -0
  94. package/docs/reference/01-core.mdx +4 -2
  95. package/docs/reference/02-server.mdx +5 -1
  96. package/docs/reference/04-ai.mdx +13 -4
  97. package/docs/reference/06-errors.mdx +1 -1
  98. package/docs/server/01-consumers.mdx +4 -1
  99. package/docs/server/06-guarantees.mdx +7 -4
  100. package/docs/stores/01-overview.mdx +1 -0
  101. package/docs/stores/02-redis.mdx +18 -10
  102. package/docs/stores/03-redis-http.mdx +6 -4
  103. package/docs/stores/04-postgres.mdx +2 -2
  104. package/docs/stores/07-valkey.mdx +97 -0
  105. package/docs/upgrades/01-overview.mdx +1 -0
  106. package/docs/upgrades/78-0-22.mdx +115 -0
  107. package/examples/playground/app/a0/sandbox.ts +58 -13
  108. package/examples/playground/app/a2slack/db/a2.ts +7 -4
  109. package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
  110. package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
  111. package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +8 -1
  112. package/examples/playground/app/chat/server.ts +2 -2
  113. package/examples/playground/lib/store.ts +1 -1
  114. package/examples/playground/package.json +1 -1
  115. package/package.json +8 -2
  116. package/sql/postgres/drizzle-schema.ts +7 -4
  117. package/sql/postgres/schema.sql +24 -3
  118. package/src/actor-shared.ts +10 -2
  119. package/src/ai-agent.ts +13 -0
  120. package/src/ai-fx.ts +365 -78
  121. package/src/ai-harness.ts +7 -0
  122. package/src/client.ts +19 -2
  123. package/src/deterministic-id.ts +1 -1
  124. package/src/internal.ts +42 -11
  125. package/src/libfx.d.ts +2 -0
  126. package/src/presence-recovery.ts +18 -11
  127. package/src/presence-tombstones.ts +52 -0
  128. package/src/push-envelope.ts +2 -2
  129. package/src/server.ts +49 -15
  130. package/src/store-memory.ts +24 -32
  131. package/src/store-postgres.ts +43 -53
  132. package/src/store-presence-polling.ts +59 -34
  133. package/src/store-redis-core.ts +595 -439
  134. package/src/store-redis-notify.ts +82 -28
  135. package/src/store-redis.ts +2 -3
  136. package/src/store-sqlite.ts +34 -40
  137. package/src/store-valkey.ts +376 -0
  138. package/src/store.ts +14 -5
  139. package/dist/actor-HnGmrOZZ.d.ts.map +0 -1
  140. package/dist/client-9IeTsiAs.js.map +0 -1
  141. package/dist/server-DvIB8kj5.js.map +0 -1
  142. package/dist/session-clear-Bk_29eEw.d.ts.map +0 -1
  143. package/dist/store-Z2qjw50n.d.ts.map +0 -1
  144. package/dist/store-presence-polling-CzQ0wdtM.js +0 -94
  145. package/dist/store-presence-polling-CzQ0wdtM.js.map +0 -1
  146. package/dist/store-redis-notify-IiVdlYHc.js.map +0 -1
  147. package/dist/stream-buffer-Cx7AAPGq.js +0 -23
  148. 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,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
- async function runStep(step) {
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 agent;
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
- for (const plan of turns) {
52
- if (stopped) return;
53
- const fxTurn = agent.prompt(plan.prompt, { signal: step.signal });
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
- 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
- });
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 compaction = isCompaction(body);
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
- const hostTools = await kernelTools(step.tools, (toolName, toolInput) => {
206
- const state = turn;
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 !== 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
- }
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 pending(void 0);
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 plans = (turns) => Promise.all(turns.map((recorded) => turnPlan(recorded, step.signal)));
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) => 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
- });
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
- try {
253
- agent = await create(read.saved);
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
- agent = await create(void 0);
429
+ kernel = await create(void 0);
430
+ turns = await planTurns(read.turns);
259
431
  }
260
- await run(streamController, await plans(read.turns));
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?.close().catch(() => {});
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 = JSON.parse(body).providerOptions;
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
- async function kernelTools(tools, execute) {
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);