experimental-a2 0.12.0 → 0.14.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 (106) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/{actor-DJi3RsNu.d.ts → actor-BfQSE0KC.d.ts} +4 -4
  3. package/dist/{actor-DJi3RsNu.d.ts.map → actor-BfQSE0KC.d.ts.map} +1 -1
  4. package/dist/actor-client.d.ts +1 -1
  5. package/dist/actor-client.js +1 -1
  6. package/dist/actor-react.d.ts +3 -3
  7. package/dist/actor-react.js +2 -2
  8. package/dist/{actor-shared-DI7J5upy.js → actor-shared-B5tJfzt-.js} +2 -2
  9. package/dist/{actor-shared-DI7J5upy.js.map → actor-shared-B5tJfzt-.js.map} +1 -1
  10. package/dist/actor.d.ts +1 -1
  11. package/dist/actor.js +3 -3
  12. package/dist/ai-Cai-lCbj.d.ts +580 -0
  13. package/dist/ai-Cai-lCbj.d.ts.map +1 -0
  14. package/dist/ai-control-CcD4hh3y.js +119 -0
  15. package/dist/ai-control-CcD4hh3y.js.map +1 -0
  16. package/dist/ai-server.d.ts +16 -8
  17. package/dist/ai-server.d.ts.map +1 -1
  18. package/dist/ai-server.js +1322 -475
  19. package/dist/ai-server.js.map +1 -1
  20. package/dist/ai.d.ts +2 -334
  21. package/dist/ai.js +838 -85
  22. package/dist/ai.js.map +1 -1
  23. package/dist/{client-P_NNNRM-.d.ts → client-BAEABRZB.d.ts} +2 -2
  24. package/dist/{client-P_NNNRM-.d.ts.map → client-BAEABRZB.d.ts.map} +1 -1
  25. package/dist/{client-Bf6uSEAk.js → client-BYzHjkwU.js} +21 -6
  26. package/dist/client-BYzHjkwU.js.map +1 -0
  27. package/dist/client.d.ts +1 -1
  28. package/dist/client.js +1 -1
  29. package/dist/{contract-48bUMgcL.js → contract-CKRg_E4q.js} +3 -26
  30. package/dist/contract-CKRg_E4q.js.map +1 -0
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +1 -1
  33. package/dist/react.d.ts +2 -2
  34. package/dist/react.js +1 -1
  35. package/dist/{reducer-DJKWm3cp.d.ts → reducer-BcS9VDKC.d.ts} +4 -1
  36. package/dist/{reducer-DJKWm3cp.d.ts.map → reducer-BcS9VDKC.d.ts.map} +1 -1
  37. package/dist/reducer-DEMjEY_O.js +29 -0
  38. package/dist/reducer-DEMjEY_O.js.map +1 -0
  39. package/dist/scheduler-qstash.d.ts +2 -2
  40. package/dist/scheduler-qstash.js +1 -1
  41. package/dist/scheduler-vercel.d.ts +2 -2
  42. package/dist/scheduler-vercel.js +1 -1
  43. package/dist/{server-DjZZa1wr.d.ts → server-Bp5Nd1pF.d.ts} +3 -3
  44. package/dist/{server-DjZZa1wr.d.ts.map → server-Bp5Nd1pF.d.ts.map} +1 -1
  45. package/dist/{server-BeNADlCI.js → server-CjJSGcF7.js} +4 -3
  46. package/dist/server-CjJSGcF7.js.map +1 -0
  47. package/dist/server.d.ts +3 -3
  48. package/dist/server.js +1 -1
  49. package/dist/{store-DtDOWLSn.d.ts → store-D_yhNdPz.d.ts} +7 -2
  50. package/dist/{store-DtDOWLSn.d.ts.map → store-D_yhNdPz.d.ts.map} +1 -1
  51. package/dist/store-N8PXxDAS.js.map +1 -1
  52. package/dist/store-memory.d.ts +1 -1
  53. package/dist/store-postgres.d.ts +1 -1
  54. package/dist/store-postgres.js +19 -0
  55. package/dist/store-postgres.js.map +1 -1
  56. package/dist/store-redis-http.d.ts +1 -1
  57. package/dist/store-redis-http.js +1 -1
  58. package/dist/{store-redis-notify-BUCyXOn0.js → store-redis-notify-D2EI6gwX.js} +27 -2
  59. package/dist/store-redis-notify-D2EI6gwX.js.map +1 -0
  60. package/dist/store-redis.d.ts +1 -1
  61. package/dist/store-redis.js +1 -1
  62. package/dist/store-sqlite.d.ts +1 -1
  63. package/docs/guides/06-ai-agents.mdx +361 -76
  64. package/docs/reference/01-api.mdx +159 -27
  65. package/examples/playground/app/agent/[agentId]/agent-client.tsx +145 -33
  66. package/examples/playground/app/agent/[agentId]/agent-queue.tsx +289 -0
  67. package/examples/playground/app/agent/[agentId]/compaction/route.ts +14 -0
  68. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +38 -0
  69. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +294 -0
  70. package/examples/playground/app/agent/compaction-settings.test.ts +144 -0
  71. package/examples/playground/app/agent/compaction-settings.ts +49 -0
  72. package/examples/playground/app/agent/compaction-timeline.test.ts +337 -0
  73. package/examples/playground/app/agent/compaction-timeline.ts +198 -0
  74. package/examples/playground/app/agent/model.ts +56 -1
  75. package/examples/playground/app/agent/server.ts +9 -2
  76. package/examples/playground/app/chat/[chatId]/chat-client.tsx +3 -13
  77. package/examples/playground/app/chat/model.ts +2 -2
  78. package/examples/playground/app/chat/server.ts +24 -17
  79. package/examples/playground/app/globals.css +333 -0
  80. package/examples/playground/package.json +1 -1
  81. package/package.json +1 -1
  82. package/src/ai-client-state.ts +185 -0
  83. package/src/ai-control-server.ts +829 -0
  84. package/src/ai-control-state.ts +152 -0
  85. package/src/ai-control.ts +139 -0
  86. package/src/ai-coordinator.ts +99 -32
  87. package/src/ai-model-metadata.ts +108 -0
  88. package/src/ai-progress-batches.ts +68 -0
  89. package/src/ai-projector.ts +76 -15
  90. package/src/ai-sdk-step.ts +5 -2
  91. package/src/ai-server.ts +920 -638
  92. package/src/ai.ts +650 -110
  93. package/src/client.ts +31 -9
  94. package/src/licenses/Apache-2.0.txt +55 -0
  95. package/src/parse-partial-json.ts +441 -0
  96. package/src/reducer.ts +6 -0
  97. package/src/server.ts +8 -4
  98. package/src/store-postgres.ts +27 -0
  99. package/src/store-redis-core.ts +53 -1
  100. package/src/store-redis-notify.ts +1 -0
  101. package/src/store.ts +6 -0
  102. package/dist/ai.d.ts.map +0 -1
  103. package/dist/client-Bf6uSEAk.js.map +0 -1
  104. package/dist/contract-48bUMgcL.js.map +0 -1
  105. package/dist/server-BeNADlCI.js.map +0 -1
  106. package/dist/store-redis-notify-BUCyXOn0.js.map +0 -1
package/dist/ai.js CHANGED
@@ -1,13 +1,407 @@
1
1
  import { n as validateSync, t as assertSyncSchema } from "./validate-XKT4FSNn.js";
2
- import { t as contract } from "./contract-48bUMgcL.js";
2
+ import { t as clientStateProjection } from "./reducer-DEMjEY_O.js";
3
+ import { t as contract } from "./contract-CKRg_E4q.js";
3
4
  import { l as ambientToolScopeStorage } from "./internal-Dq2qYxou.js";
5
+ import { n as applyControlChanges, t as initialControlState } from "./ai-control-CcD4hh3y.js";
6
+ //#region src/ai-client-state.ts
7
+ const isQueueCommand = (command) => command.action === "send" || command.action === "edit" || command.action === "move" || command.action === "remove" || command.action === "send-now" || command.action === "steer" || command.action === "pause" || command.action === "resume";
8
+ const moveItem = ({ items, inputId, beforeId }) => {
9
+ const from = items.findIndex((item) => item.id === inputId);
10
+ if (from < 0 || inputId === beforeId || beforeId !== null && !items.some((item) => item.id === beforeId)) return items;
11
+ const next = [...items];
12
+ const [item] = next.splice(from, 1);
13
+ const to = beforeId === null ? next.length : next.findIndex((entry) => entry.id === beforeId);
14
+ next.splice(to, 0, item);
15
+ return next;
16
+ };
17
+ function projectAIClientState(state) {
18
+ const pending = state.pendingQueueCommands ?? [];
19
+ if (state.status === "closed") return state;
20
+ if (pending.length === 0 && !state.inbox.items.some((item) => item.afterStepOf !== void 0)) return state;
21
+ let messages = state.messages;
22
+ let { items, paused } = state.inbox;
23
+ let turnId = state.active?.turnId ?? null;
24
+ let starting = false;
25
+ let suspended = state.active?.phase === "paused" || state.active?.phase === "pausing";
26
+ let messageIds;
27
+ const show = (message) => {
28
+ messageIds ??= new Set(messages.map((item) => item.id));
29
+ if (messageIds.has(message.id)) return;
30
+ if (messages === state.messages) messages = state.messages.slice();
31
+ messages.push(message);
32
+ messageIds.add(message.id);
33
+ };
34
+ const admit = () => {
35
+ if (turnId !== null || paused || items.length === 0) return;
36
+ let count = 0;
37
+ for (const item of items) {
38
+ count += 1;
39
+ show(item.message);
40
+ if (item.generate) {
41
+ turnId = item.id;
42
+ starting = true;
43
+ suspended = false;
44
+ break;
45
+ }
46
+ }
47
+ if (count > 0) items = items.slice(count);
48
+ };
49
+ for (const { command } of pending) {
50
+ switch (command.action) {
51
+ case "send":
52
+ case "steer": {
53
+ if (items.some((item) => item.id === command.message.id) || turnId === command.message.id) break;
54
+ const item = {
55
+ ...command.action === "steer" ? { afterStepOf: command.turnId } : {},
56
+ id: command.message.id,
57
+ revision: 0,
58
+ message: command.message,
59
+ generate: command.message.role === "user" && (command.action === "steer" || command.generate !== false)
60
+ };
61
+ items = command.action === "steer" ? [item, ...items] : [...items, item];
62
+ break;
63
+ }
64
+ case "edit":
65
+ items = items.map((item) => item.id === command.message.id && item.revision === command.expectedRevision && item.message.role === command.message.role ? {
66
+ ...item,
67
+ message: command.message
68
+ } : item);
69
+ break;
70
+ case "move":
71
+ items = moveItem({
72
+ items,
73
+ inputId: command.inputId,
74
+ beforeId: command.beforeId
75
+ });
76
+ break;
77
+ case "remove":
78
+ items = items.filter((item) => item.id !== command.inputId);
79
+ break;
80
+ case "send-now": {
81
+ if (turnId !== command.turnId) break;
82
+ const selected = items.find((item) => item.id === command.inputId);
83
+ if (!selected || selected.message.role !== "user") break;
84
+ items = items.map((item) => item.id === selected.id ? {
85
+ ...item,
86
+ generate: true,
87
+ ...turnId === null ? {} : { afterStepOf: turnId }
88
+ } : item);
89
+ items = moveItem({
90
+ items,
91
+ inputId: command.inputId,
92
+ beforeId: items[0]?.id ?? null
93
+ });
94
+ break;
95
+ }
96
+ case "pause":
97
+ paused = true;
98
+ if (command.when === "now") suspended = true;
99
+ break;
100
+ case "resume":
101
+ paused = false;
102
+ suspended = false;
103
+ }
104
+ admit();
105
+ }
106
+ items = items.filter((item) => {
107
+ if (item.afterStepOf === void 0) return true;
108
+ show(item.message);
109
+ return false;
110
+ });
111
+ return {
112
+ ...state,
113
+ messages,
114
+ inbox: {
115
+ items,
116
+ paused
117
+ },
118
+ starting: starting && !suspended
119
+ };
120
+ }
121
+ //#endregion
122
+ //#region src/parse-partial-json.ts
123
+ /*!
124
+ * Adapted from AI SDK 7.0.58, packages/ai/src/util/fix-json.ts.
125
+ * Source: https://github.com/vercel/ai, packages/ai/src/util/fix-json.ts.
126
+ * Modified to expose a synchronous partial parser with strict TypeScript types.
127
+ * Also preserves positive exponents in unfinished containers.
128
+ * Copyright 2023 Vercel, Inc.
129
+ * @license Apache-2.0. See src/licenses/Apache-2.0.txt.
130
+ */
131
+ function parsePartialJSON(text) {
132
+ if (text === void 0 || text.trim() === "") return void 0;
133
+ try {
134
+ return JSON.parse(text);
135
+ } catch {
136
+ try {
137
+ return JSON.parse(repairPartialJSON(text));
138
+ } catch {
139
+ return;
140
+ }
141
+ }
142
+ }
143
+ const isHexDigit = (char) => char >= "0" && char <= "9" || char >= "A" && char <= "F" || char >= "a" && char <= "f";
144
+ function repairPartialJSON(input) {
145
+ const stack = ["ROOT"];
146
+ let lastValidIndex = -1;
147
+ let literalStart = null;
148
+ let unicodeEscapeDigits = 0;
149
+ function processValueStart(char, i, swapState) {
150
+ switch (char) {
151
+ case "\"":
152
+ lastValidIndex = i;
153
+ stack.pop();
154
+ stack.push(swapState);
155
+ stack.push("INSIDE_STRING");
156
+ break;
157
+ case "f":
158
+ case "t":
159
+ case "n":
160
+ lastValidIndex = i;
161
+ literalStart = i;
162
+ stack.pop();
163
+ stack.push(swapState);
164
+ stack.push("INSIDE_LITERAL");
165
+ break;
166
+ case "-":
167
+ stack.pop();
168
+ stack.push(swapState);
169
+ stack.push("INSIDE_NUMBER");
170
+ break;
171
+ case "0":
172
+ case "1":
173
+ case "2":
174
+ case "3":
175
+ case "4":
176
+ case "5":
177
+ case "6":
178
+ case "7":
179
+ case "8":
180
+ case "9":
181
+ lastValidIndex = i;
182
+ stack.pop();
183
+ stack.push(swapState);
184
+ stack.push("INSIDE_NUMBER");
185
+ break;
186
+ case "{":
187
+ lastValidIndex = i;
188
+ stack.pop();
189
+ stack.push(swapState);
190
+ stack.push("INSIDE_OBJECT_START");
191
+ break;
192
+ case "[":
193
+ lastValidIndex = i;
194
+ stack.pop();
195
+ stack.push(swapState);
196
+ stack.push("INSIDE_ARRAY_START");
197
+ }
198
+ }
199
+ function processAfterObjectValue(char, i) {
200
+ switch (char) {
201
+ case ",":
202
+ stack.pop();
203
+ stack.push("INSIDE_OBJECT_AFTER_COMMA");
204
+ break;
205
+ case "}":
206
+ lastValidIndex = i;
207
+ stack.pop();
208
+ }
209
+ }
210
+ function processAfterArrayValue(char, i) {
211
+ switch (char) {
212
+ case ",":
213
+ stack.pop();
214
+ stack.push("INSIDE_ARRAY_AFTER_COMMA");
215
+ break;
216
+ case "]":
217
+ lastValidIndex = i;
218
+ stack.pop();
219
+ }
220
+ }
221
+ for (let i = 0; i < input.length; i++) {
222
+ const char = input[i];
223
+ switch (stack[stack.length - 1]) {
224
+ case "ROOT":
225
+ processValueStart(char, i, "FINISH");
226
+ break;
227
+ case "INSIDE_OBJECT_START":
228
+ switch (char) {
229
+ case "\"":
230
+ stack.pop();
231
+ stack.push("INSIDE_OBJECT_KEY");
232
+ break;
233
+ case "}":
234
+ lastValidIndex = i;
235
+ stack.pop();
236
+ }
237
+ break;
238
+ case "INSIDE_OBJECT_AFTER_COMMA":
239
+ switch (char) {
240
+ case "\"":
241
+ stack.pop();
242
+ stack.push("INSIDE_OBJECT_KEY");
243
+ }
244
+ break;
245
+ case "INSIDE_OBJECT_KEY":
246
+ switch (char) {
247
+ case "\"":
248
+ stack.pop();
249
+ stack.push("INSIDE_OBJECT_AFTER_KEY");
250
+ }
251
+ break;
252
+ case "INSIDE_OBJECT_AFTER_KEY":
253
+ switch (char) {
254
+ case ":":
255
+ stack.pop();
256
+ stack.push("INSIDE_OBJECT_BEFORE_VALUE");
257
+ }
258
+ break;
259
+ case "INSIDE_OBJECT_BEFORE_VALUE":
260
+ processValueStart(char, i, "INSIDE_OBJECT_AFTER_VALUE");
261
+ break;
262
+ case "INSIDE_OBJECT_AFTER_VALUE":
263
+ processAfterObjectValue(char, i);
264
+ break;
265
+ case "INSIDE_STRING":
266
+ switch (char) {
267
+ case "\"":
268
+ stack.pop();
269
+ lastValidIndex = i;
270
+ break;
271
+ case "\\":
272
+ stack.push("INSIDE_STRING_ESCAPE");
273
+ break;
274
+ default: lastValidIndex = i;
275
+ }
276
+ break;
277
+ case "INSIDE_ARRAY_START":
278
+ switch (char) {
279
+ case "]":
280
+ lastValidIndex = i;
281
+ stack.pop();
282
+ break;
283
+ default:
284
+ lastValidIndex = i;
285
+ processValueStart(char, i, "INSIDE_ARRAY_AFTER_VALUE");
286
+ }
287
+ break;
288
+ case "INSIDE_ARRAY_AFTER_VALUE":
289
+ switch (char) {
290
+ case ",":
291
+ stack.pop();
292
+ stack.push("INSIDE_ARRAY_AFTER_COMMA");
293
+ break;
294
+ case "]":
295
+ lastValidIndex = i;
296
+ stack.pop();
297
+ break;
298
+ default: lastValidIndex = i;
299
+ }
300
+ break;
301
+ case "INSIDE_ARRAY_AFTER_COMMA":
302
+ processValueStart(char, i, "INSIDE_ARRAY_AFTER_VALUE");
303
+ break;
304
+ case "INSIDE_STRING_ESCAPE":
305
+ stack.pop();
306
+ if (char === "u") {
307
+ unicodeEscapeDigits = 0;
308
+ stack.push("INSIDE_STRING_UNICODE_ESCAPE");
309
+ } else lastValidIndex = i;
310
+ break;
311
+ case "INSIDE_STRING_UNICODE_ESCAPE":
312
+ if (isHexDigit(char)) {
313
+ unicodeEscapeDigits++;
314
+ if (unicodeEscapeDigits === 4) {
315
+ stack.pop();
316
+ lastValidIndex = i;
317
+ }
318
+ }
319
+ break;
320
+ case "INSIDE_NUMBER":
321
+ switch (char) {
322
+ case "0":
323
+ case "1":
324
+ case "2":
325
+ case "3":
326
+ case "4":
327
+ case "5":
328
+ case "6":
329
+ case "7":
330
+ case "8":
331
+ case "9":
332
+ lastValidIndex = i;
333
+ break;
334
+ case "e":
335
+ case "E":
336
+ case "-":
337
+ case "+":
338
+ case ".": break;
339
+ case ",":
340
+ stack.pop();
341
+ if (stack[stack.length - 1] === "INSIDE_ARRAY_AFTER_VALUE") processAfterArrayValue(char, i);
342
+ if (stack[stack.length - 1] === "INSIDE_OBJECT_AFTER_VALUE") processAfterObjectValue(char, i);
343
+ break;
344
+ case "}":
345
+ stack.pop();
346
+ if (stack[stack.length - 1] === "INSIDE_OBJECT_AFTER_VALUE") processAfterObjectValue(char, i);
347
+ break;
348
+ case "]":
349
+ stack.pop();
350
+ if (stack[stack.length - 1] === "INSIDE_ARRAY_AFTER_VALUE") processAfterArrayValue(char, i);
351
+ break;
352
+ default: stack.pop();
353
+ }
354
+ break;
355
+ case "INSIDE_LITERAL": {
356
+ const partialLiteral = input.substring(literalStart, i + 1);
357
+ if (!"false".startsWith(partialLiteral) && !"true".startsWith(partialLiteral) && !"null".startsWith(partialLiteral)) {
358
+ stack.pop();
359
+ if (stack[stack.length - 1] === "INSIDE_OBJECT_AFTER_VALUE") processAfterObjectValue(char, i);
360
+ else if (stack[stack.length - 1] === "INSIDE_ARRAY_AFTER_VALUE") processAfterArrayValue(char, i);
361
+ } else lastValidIndex = i;
362
+ break;
363
+ }
364
+ }
365
+ }
366
+ let result = input.slice(0, lastValidIndex + 1);
367
+ for (let i = stack.length - 1; i >= 0; i--) switch (stack[i]) {
368
+ case "INSIDE_STRING":
369
+ result += "\"";
370
+ break;
371
+ case "INSIDE_OBJECT_KEY":
372
+ case "INSIDE_OBJECT_AFTER_KEY":
373
+ case "INSIDE_OBJECT_AFTER_COMMA":
374
+ case "INSIDE_OBJECT_START":
375
+ case "INSIDE_OBJECT_BEFORE_VALUE":
376
+ case "INSIDE_OBJECT_AFTER_VALUE":
377
+ result += "}";
378
+ break;
379
+ case "INSIDE_ARRAY_START":
380
+ case "INSIDE_ARRAY_AFTER_COMMA":
381
+ case "INSIDE_ARRAY_AFTER_VALUE":
382
+ result += "]";
383
+ break;
384
+ case "INSIDE_LITERAL": {
385
+ const partialLiteral = input.substring(literalStart, input.length);
386
+ if ("true".startsWith(partialLiteral)) result += "true".slice(partialLiteral.length);
387
+ else if ("false".startsWith(partialLiteral)) result += "false".slice(partialLiteral.length);
388
+ else if ("null".startsWith(partialLiteral)) result += "null".slice(partialLiteral.length);
389
+ }
390
+ }
391
+ return result;
392
+ }
393
+ //#endregion
4
394
  //#region src/ai-projector.ts
5
395
  const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6
- const cloneMessage = (messageId, base) => structuredClone(base ?? {
396
+ const cloneMessage = (messageId, base) => base === void 0 ? {
7
397
  id: messageId,
8
398
  role: "assistant",
9
399
  parts: []
10
- });
400
+ } : {
401
+ ...base,
402
+ id: messageId,
403
+ parts: base.parts.map((part) => ({ ...part }))
404
+ };
11
405
  const currentStepParts = (parts) => {
12
406
  let start = parts.length - 1;
13
407
  while (start >= 0 && parts[start]?.["type"] !== "step-start") start -= 1;
@@ -60,21 +454,16 @@ const mergeMetadata = (current, update) => {
60
454
  ...update
61
455
  } : update;
62
456
  };
63
- const parseCompleteJSON = (value) => {
64
- try {
65
- return JSON.parse(value);
66
- } catch {
67
- return;
68
- }
69
- };
70
- /** Internal synchronous counterpart of the AI SDK UI message stream fold. */
71
457
  function projectUIMessage(options) {
458
+ return projectUIMessageStream(options).message;
459
+ }
460
+ function projectUIMessageStream(options) {
72
461
  const message = cloneMessage(options.messageId, options.base);
73
462
  const record = message;
74
463
  const parts = message.parts;
75
- const activeText = /* @__PURE__ */ new Map();
76
- const activeReasoning = /* @__PURE__ */ new Map();
77
- const partialTools = /* @__PURE__ */ new Map();
464
+ const activeText = new Map(Object.entries(options.cursor?.text ?? {}).map(([id, index]) => [id, parts[index]]));
465
+ const activeReasoning = new Map(Object.entries(options.cursor?.reasoning ?? {}).map(([id, index]) => [id, parts[index]]));
466
+ const partialTools = new Map(Object.entries(options.cursor?.tools ?? {}).map(([id, partial]) => [id, { ...partial }]));
78
467
  for (const chunk of options.chunks) switch (chunk.type) {
79
468
  case "text-start": {
80
469
  const part = {
@@ -213,7 +602,7 @@ function projectUIMessage(options) {
213
602
  toolName: partial.toolName,
214
603
  dynamic: partial.dynamic,
215
604
  state: "input-streaming",
216
- input: parseCompleteJSON(partial.text),
605
+ input: parsePartialJSON(partial.text),
217
606
  providerExecuted: partial.providerExecuted,
218
607
  providerMetadata: partial.providerMetadata,
219
608
  toolMetadata: partial.toolMetadata,
@@ -333,7 +722,6 @@ function projectUIMessage(options) {
333
722
  activeReasoning.clear();
334
723
  break;
335
724
  case "start":
336
- if (chunk.messageId !== void 0) record["id"] = chunk.messageId;
337
725
  if (chunk.messageMetadata !== void 0 && chunk.messageMetadata !== null) record["metadata"] = mergeMetadata(record["metadata"], chunk.messageMetadata);
338
726
  break;
339
727
  case "finish":
@@ -355,10 +743,28 @@ function projectUIMessage(options) {
355
743
  });
356
744
  }
357
745
  }
358
- return message;
746
+ const indexes = new Map(parts.map((part, index) => [part, index]));
747
+ return {
748
+ message,
749
+ cursor: {
750
+ text: Object.fromEntries([...activeText].map(([id, part]) => [id, indexes.get(part)])),
751
+ reasoning: Object.fromEntries([...activeReasoning].map(([id, part]) => [id, indexes.get(part)])),
752
+ tools: Object.fromEntries(partialTools)
753
+ }
754
+ };
755
+ }
756
+ function pauseUIMessage(options) {
757
+ const next = structuredClone(options.message);
758
+ next.parts = next.parts.filter((part) => !("toolCallId" in part && !options.toolCallIds.has(part.toolCallId)));
759
+ for (const part of next.parts) if ((part.type === "text" || part.type === "reasoning") && part.state === "streaming") part.state = "done";
760
+ return next;
359
761
  }
360
762
  function interruptUIMessage(message, toolError = "Tool execution was interrupted.") {
361
763
  const next = structuredClone(message);
764
+ next.parts = next.parts.filter((value) => {
765
+ const part = value;
766
+ return !isToolPart(part) || part["state"] !== "input-streaming";
767
+ });
362
768
  for (const value of next.parts) {
363
769
  if (value["type"] === "text" || value["type"] === "reasoning") {
364
770
  if (value["state"] === "streaming") value["state"] = "done";
@@ -380,7 +786,7 @@ function interruptUIMessage(message, toolError = "Tool execution was interrupted
380
786
  value["errorText"] = toolError;
381
787
  continue;
382
788
  }
383
- if (state === "input-streaming" || state === "input-available" || state === "approval-requested" || state === "approval-responded") {
789
+ if (state === "input-available" || state === "approval-requested" || state === "approval-responded") {
384
790
  if (state === "approval-requested") delete value["approval"];
385
791
  value["state"] = "output-error";
386
792
  value["errorText"] = toolError;
@@ -389,6 +795,63 @@ function interruptUIMessage(message, toolError = "Tool execution was interrupted
389
795
  return next;
390
796
  }
391
797
  //#endregion
798
+ //#region src/ai-progress-batches.ts
799
+ const PAGE_SIZE = 64;
800
+ const appendProgressBatch = ({ batches, batch }) => {
801
+ if (batches.tail.length < PAGE_SIZE) return {
802
+ ...batches,
803
+ length: batches.length + 1,
804
+ tail: [...batches.tail, batch]
805
+ };
806
+ const blocks = [...batches.blocks];
807
+ let block = batches.tail;
808
+ let level = 0;
809
+ for (;;) {
810
+ const left = blocks[level];
811
+ if (left == null) break;
812
+ block = {
813
+ left,
814
+ right: block
815
+ };
816
+ blocks[level] = null;
817
+ level += 1;
818
+ }
819
+ blocks[level] = block;
820
+ return {
821
+ length: batches.length + 1,
822
+ tail: [batch],
823
+ blocks
824
+ };
825
+ };
826
+ const progressBatches = (items = []) => {
827
+ let batches = {
828
+ length: 0,
829
+ tail: [],
830
+ blocks: []
831
+ };
832
+ for (const batch of items) batches = appendProgressBatch({
833
+ batches,
834
+ batch
835
+ });
836
+ return batches;
837
+ };
838
+ const flattenProgressBatches = (batches) => {
839
+ const items = [];
840
+ const visit = (block) => {
841
+ if (Array.isArray(block)) items.push(...block);
842
+ else {
843
+ visit(block.left);
844
+ visit(block.right);
845
+ }
846
+ };
847
+ for (let level = batches.blocks.length - 1; level >= 0; level -= 1) {
848
+ const block = batches.blocks[level];
849
+ if (block) visit(block);
850
+ }
851
+ items.push(...batches.tail);
852
+ return items;
853
+ };
854
+ //#endregion
392
855
  //#region src/ai.ts
393
856
  const issue = (message) => ({ issues: [{ message }] });
394
857
  const schema = (label, parse) => ({ "~standard": {
@@ -419,6 +882,7 @@ const jsonObject = (label, check) => schema(label, (value) => {
419
882
  return { value };
420
883
  });
421
884
  const stringField = (value, key) => typeof value[key] === "string" && value[key].length > 0;
885
+ const optionalIndexField = (value, key) => value[key] === void 0 || typeof value[key] === "number" && Number.isSafeInteger(value[key]) && value[key] >= 0;
422
886
  const optionalStringField = (value, key) => value[key] === void 0 || typeof value[key] === "string";
423
887
  const messageValue = (value) => isRecord(value) && typeof value["id"] === "string" && (value["role"] === "user" || value["role"] === "assistant" || value["role"] === "system") && Array.isArray(value["parts"]) && value["parts"].every((part) => isRecord(part) && typeof part["type"] === "string") && isJSONCompatible(value);
424
888
  const validateMessage = (value, messageSchema) => {
@@ -442,11 +906,11 @@ const progressPayloadSchema = schema("ai.generation.progress", (value) => {
442
906
  return { value };
443
907
  });
444
908
  const completedPayloadSchema = schema("ai.generation.completed", (value) => {
445
- if (!isRecord(value) || !isJSONCompatible(value) || !stringField(value, "requestId") || !stringField(value, "messageId") || !stringField(value, "generationId") || !stringField(value, "responseMessageId") || !optionalStringField(value, "finishReason")) return issue("invalid ai.generation.completed payload");
909
+ if (!isRecord(value) || !isJSONCompatible(value) || !stringField(value, "requestId") || !stringField(value, "messageId") || !stringField(value, "generationId") || !stringField(value, "responseMessageId") || !optionalIndexField(value, "inputTokenEstimate") || !optionalStringField(value, "finishReason")) return issue("invalid ai.generation.completed payload");
446
910
  return { value };
447
911
  });
448
912
  const compactionPayloadSchema = (messageSchema) => schema("ai.compaction.completed", (value) => {
449
- if (!isRecord(value) || !isJSONCompatible(value) || !stringField(value, "generationId") || !stringField(value, "throughMessageId") || !Array.isArray(value["messages"]) || value["retainedMessageIds"] !== void 0 && (!Array.isArray(value["retainedMessageIds"]) || !value["retainedMessageIds"].every((candidate) => typeof candidate === "string"))) return issue("invalid ai.compaction.completed payload");
913
+ if (!isRecord(value) || !isJSONCompatible(value) || !stringField(value, "generationId") || !stringField(value, "throughMessageId") || !optionalIndexField(value, "throughIndex") || !optionalStringField(value, "summary") || !Array.isArray(value["messages"]) || value["retainedMessageIds"] !== void 0 && (!Array.isArray(value["retainedMessageIds"]) || !value["retainedMessageIds"].every((candidate) => typeof candidate === "string"))) return issue("invalid ai.compaction.completed payload");
450
914
  const messages = [];
451
915
  for (const candidate of value["messages"]) {
452
916
  const result = validateMessage(candidate, messageSchema);
@@ -456,22 +920,91 @@ const compactionPayloadSchema = (messageSchema) => schema("ai.compaction.complet
456
920
  return { value: {
457
921
  generationId: value["generationId"],
458
922
  throughMessageId: value["throughMessageId"],
923
+ ...value["throughIndex"] === void 0 ? {} : { throughIndex: value["throughIndex"] },
459
924
  messages,
925
+ ...value["summary"] === void 0 ? {} : { summary: value["summary"] },
926
+ ...value["usage"] === void 0 ? {} : { usage: value["usage"] },
460
927
  ...value["retainedMessageIds"] === void 0 ? {} : { retainedMessageIds: value["retainedMessageIds"] }
461
928
  } };
462
929
  });
463
930
  function createEvents(options) {
464
931
  const messageSchema = options?.messageSchema;
465
932
  if (messageSchema) assertSyncSchema(messageSchema, "the AI message schema");
466
- return {
933
+ const definition = {
934
+ "ai.control.requested": schema("ai.control.requested", (value) => {
935
+ if (!isRecord(value) || !isJSONCompatible(value)) return issue("invalid AI command");
936
+ const action = value["action"];
937
+ const forwarded = {
938
+ approval: ["ai.approval.responded", "response"],
939
+ "tool-result": ["ai.tool.result", "result"],
940
+ input: ["ai.input.responded", "response"],
941
+ "request-input": ["ai.input.requested", "request"],
942
+ retry: ["ai.retry.requested", "request"],
943
+ interrupt: ["ai.message.interrupted", "request"]
944
+ };
945
+ if (typeof action === "string" && Object.hasOwn(forwarded, action)) {
946
+ const [type, key] = forwarded[action];
947
+ const payload = validateSync(definition[type], value[key], "AI command");
948
+ if (payload.issues) return { issues: payload.issues };
949
+ return { value: {
950
+ action,
951
+ [key]: payload.value
952
+ } };
953
+ }
954
+ if (action === "send" || action === "edit" || action === "steer") {
955
+ const parsed = validateMessage(value["message"], messageSchema);
956
+ if (parsed.issues) return parsed;
957
+ if (action === "edit" && (!Number.isSafeInteger(value["expectedRevision"]) || Number(value["expectedRevision"]) < 0) || action === "steer" && (!stringField(value, "turnId") || !optionalIndexField(value, "lastSeenIndex")) || action === "send" && value["generate"] !== void 0 && typeof value["generate"] !== "boolean") return issue("invalid AI command");
958
+ return { value: {
959
+ ...value,
960
+ message: parsed.value
961
+ } };
962
+ }
963
+ if (action === "remove" && stringField(value, "inputId") || action === "send-now" && stringField(value, "inputId") && (value["turnId"] === null || stringField(value, "turnId")) && optionalIndexField(value, "lastSeenIndex") || action === "move" && stringField(value, "inputId") && (value["beforeId"] === null || stringField(value, "beforeId")) || action === "stop" && stringField(value, "turnId") && optionalIndexField(value, "lastSeenIndex") || action === "pause" && (value["when"] === "now" || value["when"] === "after-turn") || action === "resume") return { value };
964
+ return issue("invalid AI command");
965
+ }),
966
+ "ai.control.committed": jsonObject("ai.control.committed", (value) => Array.isArray(value["changes"]) && isRecord(value["view"])),
967
+ "ai.control.decided": jsonObject("ai.control.decided", (value) => stringField(value, "commandId") && (value["outcome"] === "applied" || value["outcome"] === "rejected")),
968
+ "ai.work.reported": schema("ai.work.reported", (value) => {
969
+ if (!isRecord(value) || !isJSONCompatible(value) || !stringField(value, "workId") || !stringField(value, "turnId") || typeof value["version"] !== "number" || !optionalIndexField(value, "version") || typeof value["attempt"] !== "number" || !Number.isSafeInteger(value["attempt"]) || value["attempt"] < 1 || typeof value["settled"] !== "boolean" || value["kind"] !== "model" && value["kind"] !== "tool" || !Array.isArray(value["events"])) return issue("invalid AI work report");
970
+ const allowed = /* @__PURE__ */ new Set([
971
+ "ai.generation.started",
972
+ "ai.generation.completed",
973
+ "ai.generation.failed",
974
+ "ai.tool.called",
975
+ "ai.tool.result",
976
+ "ai.approval.requested",
977
+ "ai.approval.responded",
978
+ "ai.compaction.requested",
979
+ "ai.compaction.completed",
980
+ "ai.model.metadata.requested"
981
+ ]);
982
+ const events = [];
983
+ for (const event of value["events"]) {
984
+ if (!isRecord(event) || !stringField(event, "id") || typeof event["type"] !== "string" || !allowed.has(event["type"]) || value["kind"] === "tool" && event["type"] !== "ai.tool.result") return issue("invalid AI work fact");
985
+ const parsed = validateSync(definition[event["type"]], event["payload"], "AI work fact");
986
+ if (parsed.issues) return { issues: parsed.issues };
987
+ events.push({
988
+ id: event["id"],
989
+ type: event["type"],
990
+ payload: parsed.value
991
+ });
992
+ }
993
+ return { value: {
994
+ ...value,
995
+ events
996
+ } };
997
+ }),
998
+ "ai.tool.execution.requested": jsonObject("ai.tool.execution.requested", (value) => stringField(value, "turnId") && optionalIndexField(value, "version") && isRecord(value["call"]) && isRecord(value["generation"])),
999
+ "ai.generation.paused": jsonObject("ai.generation.paused", (value) => stringField(value, "generationId") && stringField(value, "messageId")),
467
1000
  "ai.session.created": jsonObject("ai.session.created", () => true),
468
1001
  "ai.session.closed": jsonObject("ai.session.closed", (value) => optionalStringField(value, "reason")),
469
1002
  "ai.message.created": messagePayloadSchema(messageSchema),
470
1003
  "ai.message.completed": jsonObject("ai.message.completed", (value) => stringField(value, "messageId")),
471
1004
  "ai.message.interrupted": jsonObject("ai.message.interrupted", (value) => stringField(value, "messageId") && (stringField(value, "generationId") && value["requestId"] === void 0 || stringField(value, "requestId") && value["generationId"] === void 0) && optionalStringField(value, "reason") && (value["lastSeenIndex"] === void 0 || typeof value["lastSeenIndex"] === "number" && Number.isInteger(value["lastSeenIndex"]) && value["lastSeenIndex"] >= 0)),
472
- "ai.generation.requested": jsonObject("ai.generation.requested", (value) => stringField(value, "messageId") && optionalStringField(value, "responseMessageId") && (value["reason"] === "message" || value["reason"] === "tool" || value["reason"] === "input" || value["reason"] === "retry")),
1005
+ "ai.generation.requested": jsonObject("ai.generation.requested", (value) => stringField(value, "messageId") && optionalStringField(value, "responseMessageId") && (value["reason"] === "message" || value["reason"] === "tool" || value["reason"] === "input" || value["reason"] === "retry" || value["reason"] === "resume")),
473
1006
  "ai.retry.requested": jsonObject("ai.retry.requested", (value) => stringField(value, "messageId") && stringField(value, "responseMessageId") && stringField(value, "retryId")),
474
- "ai.generation.started": jsonObject("ai.generation.started", (value) => stringField(value, "requestId") && stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "responseMessageId") && typeof value["attempt"] === "number" && Number.isInteger(value["attempt"]) && value["attempt"] > 0 && stringField(value, "model")),
1007
+ "ai.generation.started": jsonObject("ai.generation.started", (value) => stringField(value, "requestId") && stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "responseMessageId") && typeof value["attempt"] === "number" && Number.isInteger(value["attempt"]) && value["attempt"] > 0 && stringField(value, "model") && optionalIndexField(value, "promptThroughIndex")),
475
1008
  "ai.generation.progress": progressPayloadSchema,
476
1009
  "ai.generation.completed": completedPayloadSchema,
477
1010
  "ai.generation.failed": jsonObject("ai.generation.failed", (value) => stringField(value, "requestId") && stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "responseMessageId") && stringField(value, "error") && (value["superseded"] === void 0 || typeof value["superseded"] === "boolean") && (value["stepLimit"] === void 0 || typeof value["stepLimit"] === "boolean")),
@@ -481,12 +1014,20 @@ function createEvents(options) {
481
1014
  "ai.approval.responded": jsonObject("ai.approval.responded", (value) => stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "approvalId") && typeof value["approved"] === "boolean" && optionalStringField(value, "reason")),
482
1015
  "ai.input.requested": jsonObject("ai.input.requested", (value) => stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "inputId") && stringField(value, "name")),
483
1016
  "ai.input.responded": jsonObject("ai.input.responded", (value) => stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "inputId") && stringField(value, "name")),
484
- "ai.compaction.requested": jsonObject("ai.compaction.requested", (value) => stringField(value, "generationId") && stringField(value, "throughMessageId")),
485
- "ai.compaction.completed": compactionPayloadSchema(messageSchema)
1017
+ "ai.compaction.requested": jsonObject("ai.compaction.requested", (value) => stringField(value, "generationId") && stringField(value, "throughMessageId") && optionalIndexField(value, "throughIndex")),
1018
+ "ai.compaction.completed": compactionPayloadSchema(messageSchema),
1019
+ "ai.model.metadata.requested": jsonObject("ai.model.metadata.requested", (value) => stringField(value, "modelId")),
1020
+ "ai.model.metadata.resolved": jsonObject("ai.model.metadata.resolved", (value) => stringField(value, "modelId") && (value["limits"] === null || isRecord(value["limits"]) && typeof value["limits"]["contextWindow"] === "number" && Number.isSafeInteger(value["limits"]["contextWindow"]) && value["limits"]["contextWindow"] > 0 && typeof value["limits"]["maxOutputTokens"] === "number" && Number.isSafeInteger(value["limits"]["maxOutputTokens"]) && value["limits"]["maxOutputTokens"] > 0))
486
1021
  };
1022
+ return definition;
487
1023
  }
488
1024
  const events = createEvents();
489
1025
  const initialState = () => ({
1026
+ inbox: initialControlState().inbox,
1027
+ pendingQueueCommands: [],
1028
+ active: null,
1029
+ starting: false,
1030
+ receipt: null,
490
1031
  session: { created: false },
491
1032
  messages: [],
492
1033
  status: "idle",
@@ -499,6 +1040,7 @@ const initialState = () => ({
499
1040
  pendingInputs: [],
500
1041
  tools: [],
501
1042
  compaction: null,
1043
+ modelMetadata: {},
502
1044
  usage: [],
503
1045
  error: null
504
1046
  });
@@ -509,6 +1051,17 @@ const upsertMessage = (messages, message) => {
509
1051
  next[index] = message;
510
1052
  return next;
511
1053
  };
1054
+ const upsertResponse = (options) => {
1055
+ const { messages, message, inputId } = options;
1056
+ if (messages.some((item) => item.id === message.id)) return upsertMessage(messages, message);
1057
+ const inputIndex = messages.findIndex((item) => item.id === inputId);
1058
+ if (inputIndex === -1) return [...messages, message];
1059
+ return [
1060
+ ...messages.slice(0, inputIndex + 1),
1061
+ message,
1062
+ ...messages.slice(inputIndex + 1)
1063
+ ];
1064
+ };
512
1065
  const answerApproval = (messages, response) => messages.map((message) => {
513
1066
  if (message.id !== response.messageId) return message;
514
1067
  let changed = false;
@@ -594,8 +1147,8 @@ const isStaleGeneration = (state, messageId, generationId) => {
594
1147
  const current = state.responseGenerationIds[messageId];
595
1148
  return current !== void 0 && current !== generationId;
596
1149
  };
597
- const projectedMessage = (projection) => {
598
- const projectionEvents = [...projection.batches];
1150
+ const projectedMessageStream = (projection) => {
1151
+ const projectionEvents = flattenProgressBatches(projection.batches);
599
1152
  for (const event of projection.toolEvents) if (event.type === "ai.tool.result") projectionEvents.push({
600
1153
  index: event.index,
601
1154
  chunks: [toolResultChunk(event.payload)]
@@ -604,12 +1157,20 @@ const projectedMessage = (projection) => {
604
1157
  index: event.index,
605
1158
  chunks: [event.type === "ai.approval.requested" ? approvalRequestedChunk(event.payload) : approvalRespondedChunk(event.payload)]
606
1159
  });
607
- return projectUIMessage({
1160
+ return projectUIMessageStream({
608
1161
  messageId: projection.responseMessageId,
609
1162
  ...projection.baseMessage === void 0 ? {} : { base: projection.baseMessage },
610
1163
  chunks: projectionEvents.toSorted((left, right) => left.index - right.index).flatMap((event) => event.chunks)
611
1164
  });
612
1165
  };
1166
+ const projectedMessage = (projection) => projectedMessageStream(projection).message;
1167
+ const projectedResponse = (options) => {
1168
+ const message = projectedMessage(options.projection);
1169
+ return options.state.pausedGenerationIds?.[options.projection.generationId] ? pauseUIMessage({
1170
+ message,
1171
+ toolCallIds: new Set(options.state.tools.map((tool) => tool.toolCallId))
1172
+ }) : message;
1173
+ };
613
1174
  const reduceToolActivity = (tools, event) => {
614
1175
  if (event.type === "ai.tool.called") {
615
1176
  const payload = event.payload;
@@ -680,7 +1241,7 @@ const interruptGenerationProjection = (state, generationId, responseMessageId, c
680
1241
  if (projection === null) return null;
681
1242
  const retained = {
682
1243
  ...projection,
683
- batches: projection.batches.filter((batch) => batch.index <= cutoff),
1244
+ batches: progressBatches(flattenProgressBatches(projection.batches).filter((batch) => batch.index <= cutoff)),
684
1245
  toolEvents: projection.toolEvents.filter((toolEvent) => toolEvent.index <= cutoff),
685
1246
  approvalEvents: projection.approvalEvents.filter((approvalEvent) => approvalEvent.index <= cutoff)
686
1247
  };
@@ -688,7 +1249,7 @@ const interruptGenerationProjection = (state, generationId, responseMessageId, c
688
1249
  const restored = restoreGenerationBase(state, generationId, responseMessageId, "interrupted");
689
1250
  return {
690
1251
  ...restored,
691
- messages: shouldProject ? upsertMessage(restored.messages, interruptUIMessage(projectedMessage(retained))) : restored.messages,
1252
+ messages: shouldProject ? upsertMessage(state.messages, interruptUIMessage(projectedMessage(retained))) : restored.messages,
692
1253
  tools: stopGenerationTools(projectedTools(retained), generationId, responseMessageId, "Tool execution was interrupted."),
693
1254
  usage: projection.completionIndex === void 0 || projection.completionIndex <= cutoff ? restored.usage : restored.usage.filter((entry) => entry.generationId !== generationId)
694
1255
  };
@@ -707,6 +1268,100 @@ const terminalGenerationReason = (state, generationId) => {
707
1268
  function reduceAIState(state, event) {
708
1269
  if (state.status === "closed") return state;
709
1270
  switch (event.type) {
1271
+ case "ai.control.requested": {
1272
+ const command = event.payload;
1273
+ if (!event.id || !isQueueCommand(command)) return state;
1274
+ const pending = state.pendingQueueCommands ?? [];
1275
+ if (pending.some((item) => item.commandId === event.id)) return state;
1276
+ if ((command.action === "send" || command.action === "steer") && (state.messages.some((message) => message.id === command.message.id) || state.inbox.items.some((item) => item.id === command.message.id))) return state;
1277
+ return {
1278
+ ...state,
1279
+ pendingQueueCommands: [...pending, {
1280
+ commandId: event.id,
1281
+ command
1282
+ }]
1283
+ };
1284
+ }
1285
+ case "ai.control.committed": {
1286
+ const commit = event.payload;
1287
+ const visible = applyControlChanges({
1288
+ state: {
1289
+ inbox: state.inbox,
1290
+ active: state.active
1291
+ },
1292
+ changes: commit.changes.filter((change) => change.path[0] === "inbox" || change.path[0] === "active")
1293
+ });
1294
+ const { phase } = commit.view;
1295
+ const status = commit.view.closed ? "closed" : phase === "paused" || phase === "pausing" || commit.view.paused && phase === null ? "paused" : phase === "failed" ? "failed" : phase === "starting" || phase === "generating" ? "generating" : phase ? "waiting" : "idle";
1296
+ return {
1297
+ ...state,
1298
+ ...visible,
1299
+ status,
1300
+ activeRequestId: commit.view.requestId,
1301
+ activeResponseMessageId: commit.view.responseMessageId
1302
+ };
1303
+ }
1304
+ case "ai.control.decided": {
1305
+ const receipt = event.payload;
1306
+ if (receipt.work) return state;
1307
+ const pending = state.pendingQueueCommands ?? [];
1308
+ const settled = pending.find((item) => item.commandId === receipt.commandId);
1309
+ return {
1310
+ ...state,
1311
+ receipt,
1312
+ ...settled === void 0 ? {} : { pendingQueueCommands: pending.filter((item) => item.commandId !== receipt.commandId) }
1313
+ };
1314
+ }
1315
+ case "ai.generation.paused": {
1316
+ const payload = event.payload;
1317
+ const toolCallIds = new Set(state.tools.filter((tool) => tool.messageId === payload.messageId).map((tool) => tool.toolCallId));
1318
+ const projection = state.activeProjection;
1319
+ return {
1320
+ ...state,
1321
+ activeGeneration: null,
1322
+ activeProjection: projection?.generationId === payload.generationId ? {
1323
+ ...projection,
1324
+ cursor: null,
1325
+ batches: progressBatches(flattenProgressBatches(projection.batches).map((batch) => ({
1326
+ index: batch.index,
1327
+ chunks: batch.chunks.filter((chunk) => !("toolCallId" in chunk) || toolCallIds.has(chunk.toolCallId))
1328
+ })))
1329
+ } : projection,
1330
+ compaction: state.compaction?.status === "running" ? projection?.baseCompaction ?? null : state.compaction,
1331
+ messages: state.messages.map((message) => message.id === payload.messageId ? pauseUIMessage({
1332
+ message,
1333
+ toolCallIds
1334
+ }) : message),
1335
+ pausedGenerationIds: {
1336
+ ...state.pausedGenerationIds,
1337
+ [payload.generationId]: true
1338
+ }
1339
+ };
1340
+ }
1341
+ case "ai.model.metadata.requested": {
1342
+ const { modelId } = event.payload;
1343
+ if (Object.hasOwn(state.modelMetadata, modelId)) return state;
1344
+ return {
1345
+ ...state,
1346
+ modelMetadata: {
1347
+ ...state.modelMetadata,
1348
+ [modelId]: { status: "pending" }
1349
+ }
1350
+ };
1351
+ }
1352
+ case "ai.model.metadata.resolved": {
1353
+ const { modelId, limits } = event.payload;
1354
+ return {
1355
+ ...state,
1356
+ modelMetadata: {
1357
+ ...state.modelMetadata,
1358
+ [modelId]: limits === null ? { status: "unavailable" } : {
1359
+ status: "resolved",
1360
+ limits
1361
+ }
1362
+ }
1363
+ };
1364
+ }
710
1365
  case "ai.session.created": {
711
1366
  const payload = event.payload;
712
1367
  return {
@@ -719,7 +1374,14 @@ function reduceAIState(state, event) {
719
1374
  }
720
1375
  case "ai.session.closed": return {
721
1376
  ...state,
1377
+ active: null,
1378
+ inbox: {
1379
+ paused: true,
1380
+ items: []
1381
+ },
1382
+ pendingQueueCommands: [],
722
1383
  status: "closed",
1384
+ compaction: state.compaction?.status === "running" ? state.activeProjection?.baseCompaction ?? null : state.compaction,
723
1385
  activeGeneration: null,
724
1386
  activeRequestId: null,
725
1387
  activeResponseMessageId: null,
@@ -729,7 +1391,11 @@ function reduceAIState(state, event) {
729
1391
  const { message } = event.payload;
730
1392
  return {
731
1393
  ...state,
732
- messages: upsertMessage(state.messages, message)
1394
+ messages: upsertMessage(state.messages, message),
1395
+ activeProjection: state.activeProjection?.responseMessageId === message.id ? {
1396
+ ...state.activeProjection,
1397
+ cursor: null
1398
+ } : state.activeProjection
733
1399
  };
734
1400
  }
735
1401
  case "ai.message.completed": {
@@ -760,7 +1426,7 @@ function reduceAIState(state, event) {
760
1426
  const ownerRequestId = ownerGenerationId === void 0 ? void 0 : generationRequestId(ownerGenerationId);
761
1427
  const requestId = event.id;
762
1428
  if (requestId !== void 0 && state.terminalRequestIds?.[requestId] === true) return state;
763
- if (!(payload.reason === "message" || (payload.reason === "tool" ? ownerGenerationId !== void 0 && requestId === `ai.generate:tools:${ownerGenerationId}` && state.activeRequestId === ownerRequestId && state.status !== "failed" : payload.reason === "retry" ? state.status === "failed" : ownerGenerationId !== void 0 && requestId?.startsWith(`ai.generate:input:${encodeURIComponent(responseMessageId)}:${encodeURIComponent(ownerGenerationId)}:`) === true && state.activeRequestId === ownerRequestId && state.status !== "failed"))) return state;
1429
+ if (!(payload.control !== void 0 && state.activeRequestId === event.id || payload.reason === "message" || (payload.reason === "tool" ? ownerGenerationId !== void 0 && requestId === `ai.generate:tools:${ownerGenerationId}` && state.activeRequestId === ownerRequestId && state.status !== "failed" : payload.reason === "retry" ? state.status === "failed" : ownerGenerationId !== void 0 && requestId?.startsWith(`ai.generate:input:${encodeURIComponent(responseMessageId)}:${encodeURIComponent(ownerGenerationId)}:`) === true && state.activeRequestId === ownerRequestId && state.status !== "failed"))) return state;
764
1430
  return {
765
1431
  ...state,
766
1432
  messages: payload.reason === "retry" && payload.responseMessageId !== void 0 ? state.messages.filter((message) => message.id !== payload.responseMessageId) : state.messages,
@@ -796,9 +1462,10 @@ function reduceAIState(state, event) {
796
1462
  activeProjection: {
797
1463
  generationId: payload.generationId,
798
1464
  responseMessageId: payload.responseMessageId,
1465
+ promptThroughIndex: payload.promptThroughIndex ?? event.index - 1,
799
1466
  ...baseMessage === void 0 ? {} : { baseMessage },
800
1467
  baseCompaction: baseState.compaction,
801
- batches: [],
1468
+ batches: progressBatches(),
802
1469
  baseTools: baseState.tools,
803
1470
  toolEvents: [],
804
1471
  approvalEvents: []
@@ -808,19 +1475,43 @@ function reduceAIState(state, event) {
808
1475
  }
809
1476
  case "ai.generation.progress": {
810
1477
  const payload = event.payload;
1478
+ if (state.pausedGenerationIds?.[payload.generationId]) return state;
811
1479
  if (terminalGenerationReason(state, payload.generationId) !== void 0) return state;
812
1480
  const activeProjection = state.activeProjection;
813
1481
  if (state.activeGeneration?.generationId !== payload.generationId || activeProjection?.generationId !== payload.generationId) return state;
1482
+ const ordered = event.index > (activeProjection.batches.tail.at(-1)?.index ?? -1);
814
1483
  const projection = {
815
1484
  ...activeProjection,
816
- batches: [...activeProjection.batches.filter((batch) => batch.index !== event.index), {
1485
+ batches: ordered ? appendProgressBatch({
1486
+ batches: activeProjection.batches,
1487
+ batch: {
1488
+ index: event.index,
1489
+ chunks: payload.chunks
1490
+ }
1491
+ }) : progressBatches([...flattenProgressBatches(activeProjection.batches).filter((batch) => batch.index !== event.index), {
817
1492
  index: event.index,
818
1493
  chunks: payload.chunks
819
- }].toSorted((left, right) => left.index - right.index)
1494
+ }].toSorted((left, right) => left.index - right.index))
1495
+ };
1496
+ const base = state.messages.find((message) => message.id === projection.responseMessageId);
1497
+ const incremental = base !== void 0 && activeProjection.cursor != null && event.index > activeProjection.cursor.throughIndex;
1498
+ const projected = incremental ? projectUIMessageStream({
1499
+ messageId: projection.responseMessageId,
1500
+ base,
1501
+ cursor: activeProjection.cursor.stream,
1502
+ chunks: payload.chunks
1503
+ }) : projectedMessageStream(projection);
1504
+ projection.cursor = {
1505
+ throughIndex: incremental ? event.index : Math.max(event.index, projection.batches.tail.at(-1)?.index ?? 0, projection.toolEvents.reduce((index, entry) => Math.max(index, entry.index), 0), projection.approvalEvents.reduce((index, entry) => Math.max(index, entry.index), 0)),
1506
+ stream: projected.cursor
820
1507
  };
821
1508
  return {
822
1509
  ...state,
823
- messages: upsertMessage(state.messages, projectedMessage(projection)),
1510
+ messages: upsertResponse({
1511
+ messages: state.messages,
1512
+ message: projected.message,
1513
+ inputId: state.activeGeneration.messageId
1514
+ }),
824
1515
  activeProjection: projection,
825
1516
  status: "generating"
826
1517
  };
@@ -1019,13 +1710,17 @@ function reduceAIState(state, event) {
1019
1710
  }
1020
1711
  const activeProjection = {
1021
1712
  ...projection,
1713
+ cursor: null,
1022
1714
  toolEvents: [...projection.toolEvents, {
1023
1715
  index: event.index,
1024
1716
  type: "ai.tool.result",
1025
1717
  payload
1026
1718
  }]
1027
1719
  };
1028
- const projected = projectedMessage(activeProjection);
1720
+ const projected = projectedResponse({
1721
+ state,
1722
+ projection: activeProjection
1723
+ });
1029
1724
  const tools = projectedTools(activeProjection);
1030
1725
  const failureError = state.error ?? "Generation failed.";
1031
1726
  const next = {
@@ -1046,6 +1741,7 @@ function reduceAIState(state, event) {
1046
1741
  if (state.tools.some((activity) => activity.generationId === payload.generationId && activity.toolCallId === payload.toolCallId && activity.status === "failed" && state.activeGeneration?.generationId !== payload.generationId)) return state;
1047
1742
  const activeProjection = projection?.generationId === payload.generationId ? {
1048
1743
  ...projection,
1744
+ cursor: null,
1049
1745
  approvalEvents: [...projection.approvalEvents, {
1050
1746
  index: event.index,
1051
1747
  type: "ai.approval.requested",
@@ -1054,7 +1750,10 @@ function reduceAIState(state, event) {
1054
1750
  } : projection;
1055
1751
  const next = {
1056
1752
  ...state,
1057
- messages: activeProjection === null || activeProjection === projection ? projectMessageChunks(state.messages, payload.messageId, [approvalRequestedChunk(payload)]) : upsertMessage(state.messages, projectedMessage(activeProjection)),
1753
+ messages: activeProjection === null || activeProjection === projection ? projectMessageChunks(state.messages, payload.messageId, [approvalRequestedChunk(payload)]) : upsertMessage(state.messages, projectedResponse({
1754
+ state,
1755
+ projection: activeProjection
1756
+ })),
1058
1757
  activeProjection,
1059
1758
  pendingApprovals: payload.isAutomatic === true ? state.pendingApprovals : [...state.pendingApprovals.filter((approval) => approval.approvalId !== payload.approvalId || approval.messageId !== payload.messageId), payload]
1060
1759
  };
@@ -1072,6 +1771,7 @@ function reduceAIState(state, event) {
1072
1771
  if (state.status === "failed" && pendingRequest === void 0 && activeRequest === void 0) return state;
1073
1772
  const activeProjection = projection !== null && activeRequest !== void 0 ? {
1074
1773
  ...projection,
1774
+ cursor: null,
1075
1775
  approvalEvents: [...projection.approvalEvents, {
1076
1776
  index: event.index,
1077
1777
  type: "ai.approval.responded",
@@ -1080,8 +1780,14 @@ function reduceAIState(state, event) {
1080
1780
  } : projection;
1081
1781
  const next = {
1082
1782
  ...state,
1083
- messages: activeProjection === null || activeProjection === projection ? answerApproval(state.messages, payload) : upsertMessage(state.messages, projectedMessage(activeProjection)),
1084
- activeProjection,
1783
+ messages: activeProjection === null || activeProjection === projection ? answerApproval(state.messages, payload) : upsertMessage(state.messages, projectedResponse({
1784
+ state,
1785
+ projection: activeProjection
1786
+ })),
1787
+ activeProjection: activeProjection === null ? null : {
1788
+ ...activeProjection,
1789
+ cursor: null
1790
+ },
1085
1791
  pendingApprovals: state.pendingApprovals.filter((approval) => approval.approvalId !== payload.approvalId || approval.messageId !== payload.messageId || approval.generationId !== payload.generationId)
1086
1792
  };
1087
1793
  return {
@@ -1131,7 +1837,8 @@ function reduceAIState(state, event) {
1131
1837
  ...state,
1132
1838
  compaction: {
1133
1839
  status: "completed",
1134
- ...payload
1840
+ ...payload,
1841
+ throughIndex: payload.throughIndex ?? state.activeProjection.promptThroughIndex
1135
1842
  }
1136
1843
  };
1137
1844
  }
@@ -1146,62 +1853,105 @@ function deriveUIMessages(history) {
1146
1853
  }
1147
1854
  /** Build the standard AI state projection for any contract containing the protocol. */
1148
1855
  function createReducer(options) {
1149
- return options.contract.reducer({
1150
- name: options.name ?? "a2.ai.state.v8",
1151
- initialState: initialState()
1152
- }).fold((state, event) => reduceAIState(state, event));
1856
+ return {
1857
+ ...options.contract.reducer({
1858
+ name: options.name ?? "a2.ai.state.v17",
1859
+ initialState: initialState()
1860
+ }).fold((state, event) => reduceAIState(state, event)),
1861
+ [clientStateProjection]: projectAIClientState
1862
+ };
1153
1863
  }
1864
+ const commandInput = (payload) => [{
1865
+ type: "ai.control.requested",
1866
+ payload
1867
+ }];
1154
1868
  const messageInput = (message, generate) => [{
1155
- type: "ai.message.created",
1869
+ type: "ai.control.requested",
1156
1870
  id: `ai.message:${message.id}`,
1157
1871
  payload: {
1872
+ action: "send",
1158
1873
  message,
1159
1874
  ...generate === false ? { generate: false } : {}
1160
1875
  }
1161
1876
  }];
1162
1877
  /** Pure append inputs for the built-in AI protocol. */
1163
1878
  const inputs = {
1879
+ toolResult: (result) => commandInput({
1880
+ action: "tool-result",
1881
+ result
1882
+ }),
1883
+ queue: {
1884
+ remove: (options) => commandInput({
1885
+ action: "remove",
1886
+ ...options
1887
+ }),
1888
+ sendNow: (options) => commandInput({
1889
+ action: "send-now",
1890
+ ...options
1891
+ }),
1892
+ edit: (options) => commandInput({
1893
+ action: "edit",
1894
+ ...options
1895
+ }),
1896
+ move: (options) => commandInput({
1897
+ action: "move",
1898
+ ...options
1899
+ })
1900
+ },
1901
+ stop: (options) => commandInput({
1902
+ action: "stop",
1903
+ ...options
1904
+ }),
1905
+ steer: (options) => commandInput({
1906
+ action: "steer",
1907
+ ...options
1908
+ }),
1909
+ pause: (options) => commandInput({
1910
+ action: "pause",
1911
+ ...options
1912
+ }),
1913
+ resume: () => commandInput({ action: "resume" }),
1164
1914
  message(message, options) {
1165
1915
  return messageInput(message, options?.generate);
1166
1916
  },
1167
1917
  seed(message) {
1168
1918
  return messageInput(message, false);
1169
1919
  },
1170
- approval(response) {
1171
- return [{
1172
- type: "ai.approval.responded",
1173
- id: `ai.approval:${encodeURIComponent(response.messageId)}:${encodeURIComponent(response.generationId)}:${encodeURIComponent(response.approvalId)}:response`,
1174
- payload: response
1175
- }];
1176
- },
1177
- input(response) {
1178
- return [{
1179
- type: "ai.input.responded",
1180
- id: `ai.input:${encodeURIComponent(response.messageId)}:${encodeURIComponent(response.generationId)}:${encodeURIComponent(response.inputId)}:response`,
1181
- payload: response
1182
- }];
1183
- },
1184
- requestInput(request) {
1185
- return [{
1186
- type: "ai.input.requested",
1187
- id: `ai.input:${encodeURIComponent(request.messageId)}:${encodeURIComponent(request.generationId)}:${encodeURIComponent(request.inputId)}:request`,
1188
- payload: request
1189
- }];
1190
- },
1191
- retry(retryOptions) {
1192
- return [{
1193
- type: "ai.retry.requested",
1194
- id: `ai.retry:${retryOptions.retryId}`,
1195
- payload: retryOptions
1196
- }];
1197
- },
1198
- interrupt(interruption) {
1199
- return [{
1200
- type: "ai.message.interrupted",
1201
- id: `ai.interrupt:${interruption.generationId ?? interruption.requestId}`,
1202
- payload: interruption
1203
- }];
1204
- }
1920
+ approval: (response) => [{
1921
+ ...commandInput({
1922
+ action: "approval",
1923
+ response
1924
+ })[0],
1925
+ id: `ai.approval:${encodeURIComponent(response.messageId)}:${encodeURIComponent(response.generationId)}:${encodeURIComponent(response.approvalId)}:response`
1926
+ }],
1927
+ input: (response) => [{
1928
+ ...commandInput({
1929
+ action: "input",
1930
+ response
1931
+ })[0],
1932
+ id: `ai.input:${encodeURIComponent(response.messageId)}:${encodeURIComponent(response.generationId)}:${encodeURIComponent(response.inputId)}:response`
1933
+ }],
1934
+ requestInput: (request) => [{
1935
+ ...commandInput({
1936
+ action: "request-input",
1937
+ request
1938
+ })[0],
1939
+ id: `ai.input:${encodeURIComponent(request.messageId)}:${encodeURIComponent(request.generationId)}:${encodeURIComponent(request.inputId)}:request`
1940
+ }],
1941
+ retry: (request) => [{
1942
+ ...commandInput({
1943
+ action: "retry",
1944
+ request
1945
+ })[0],
1946
+ id: `ai.retry:${request.retryId}`
1947
+ }],
1948
+ interrupt: (request) => [{
1949
+ ...commandInput({
1950
+ action: "interrupt",
1951
+ request
1952
+ })[0],
1953
+ id: `ai.interrupt:${request.generationId ?? request.requestId}`
1954
+ }]
1205
1955
  };
1206
1956
  /**
1207
1957
  * Read the durable handler context ambient to the current AI tool
@@ -1235,10 +1985,13 @@ function agent(options) {
1235
1985
  });
1236
1986
  return {
1237
1987
  contract: agentContract,
1238
- reducer: agentContract.reducer({
1239
- name: options.reducerName ?? "a2.ai.state.v8",
1240
- initialState: initialState()
1241
- }).fold((state, event) => reduceAIState(state, event))
1988
+ reducer: {
1989
+ ...agentContract.reducer({
1990
+ name: options.reducerName ?? "a2.ai.state.v17",
1991
+ initialState: initialState()
1992
+ }).fold((state, event) => reduceAIState(state, event)),
1993
+ [clientStateProjection]: projectAIClientState
1994
+ }
1242
1995
  };
1243
1996
  }
1244
1997
  //#endregion