@cuylabs/agent-core 0.8.0 → 0.10.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 (127) hide show
  1. package/README.md +33 -17
  2. package/dist/chunk-2O4MCSQS.js +780 -0
  3. package/dist/chunk-2TTOLHBT.js +198 -0
  4. package/dist/chunk-5FMSGQVX.js +281 -0
  5. package/dist/chunk-5NVVNXPQ.js +288 -0
  6. package/dist/{chunk-CAA7FHIH.js → chunk-6HZBHFOL.js} +3 -103
  7. package/dist/chunk-CJI7PVS2.js +58 -0
  8. package/dist/{chunk-N6HWIEEA.js → chunk-CMYN2RCB.js} +278 -61
  9. package/dist/chunk-FII65CN7.js +117 -0
  10. package/dist/{chunk-IVUJDISU.js → chunk-GFTW23FV.js} +5 -14
  11. package/dist/chunk-I6PKJ7XQ.js +292 -0
  12. package/dist/{chunk-BDBZ3SLK.js → chunk-ICZ66572.js} +48 -4
  13. package/dist/chunk-KYLPMBHD.js +316 -0
  14. package/dist/chunk-MXAP4UG6.js +2956 -0
  15. package/dist/{chunk-RZITT45F.js → chunk-N3VX7FEE.js} +39 -6
  16. package/dist/{chunk-YSLSEQ6B.js → chunk-NDZWXCBZ.js} +218 -95
  17. package/dist/{chunk-P6YF7USR.js → chunk-Q742PSH3.js} +23 -38
  18. package/dist/chunk-QAL3OMI3.js +943 -0
  19. package/dist/{chunk-RFEKJKTO.js → chunk-RN6WZEUF.js} +330 -280
  20. package/dist/{chunk-ZXAKHMWH.js → chunk-ROTGCYDW.js} +22 -84
  21. package/dist/chunk-SPBFQXOT.js +0 -0
  22. package/dist/{chunk-LRHOS4ZN.js → chunk-SPILYYDF.js} +3 -2
  23. package/dist/chunk-SSFBF3US.js +602 -0
  24. package/dist/chunk-SZ2XBPTW.js +8 -0
  25. package/dist/chunk-T4UIX5D7.js +115 -0
  26. package/dist/chunk-TIHPYVAJ.js +102 -0
  27. package/dist/{chunk-YUUJK53A.js → chunk-TOTDGK3P.js} +1 -1
  28. package/dist/chunk-V4RFNEET.js +563 -0
  29. package/dist/chunk-VOUEJSW6.js +0 -0
  30. package/dist/{chunk-4BDA7DQY.js → chunk-WBPOZ7CL.js} +673 -273
  31. package/dist/chunk-X4VN4GIJ.js +185 -0
  32. package/dist/dispatch/index.d.ts +93 -0
  33. package/dist/dispatch/index.js +37 -0
  34. package/dist/events/index.d.ts +93 -0
  35. package/dist/events/index.js +6 -0
  36. package/dist/{runtime → execution}/index.d.ts +120 -34
  37. package/dist/{runtime → execution}/index.js +18 -13
  38. package/dist/index-BCqEGzBj.d.ts +251 -0
  39. package/dist/index.d.ts +490 -122
  40. package/dist/index.js +2104 -615
  41. package/dist/{errors → inference/errors}/index.d.ts +2 -2
  42. package/dist/{errors → inference/errors}/index.js +1 -1
  43. package/dist/inference/index.d.ts +16 -23
  44. package/dist/inference/index.js +45 -16
  45. package/dist/instance-BqV2D5pc.d.ts +5723 -0
  46. package/dist/logger/index.d.ts +50 -0
  47. package/dist/logger/index.js +11 -0
  48. package/dist/mcp/index.d.ts +5 -9
  49. package/dist/mcp/index.js +2 -3
  50. package/dist/middleware/index.d.ts +10 -149
  51. package/dist/middleware/index.js +11 -3
  52. package/dist/model-messages-B4nK9D1-.d.ts +13 -0
  53. package/dist/models/index.d.ts +23 -18
  54. package/dist/models/index.js +48 -11
  55. package/dist/models/reasoning/index.d.ts +4 -0
  56. package/dist/{reasoning → models/reasoning}/index.js +3 -3
  57. package/dist/plugin/index.d.ts +458 -0
  58. package/dist/plugin/index.js +32 -0
  59. package/dist/profiles/index.d.ts +55 -0
  60. package/dist/profiles/index.js +30 -0
  61. package/dist/prompt/index.d.ts +8 -12
  62. package/dist/prompt/index.js +3 -2
  63. package/dist/safety/index.d.ts +109 -14
  64. package/dist/safety/index.js +59 -3
  65. package/dist/sandbox/index.d.ts +81 -0
  66. package/dist/sandbox/index.js +1 -0
  67. package/dist/skill/index.d.ts +10 -8
  68. package/dist/skill/index.js +3 -3
  69. package/dist/storage/index.d.ts +12 -4
  70. package/dist/storage/index.js +1 -1
  71. package/dist/subagents/index.d.ts +177 -0
  72. package/dist/subagents/index.js +78 -0
  73. package/dist/team/index.d.ts +544 -0
  74. package/dist/team/index.js +41 -0
  75. package/dist/tool/host/index.d.ts +41 -0
  76. package/dist/tool/host/index.js +10 -0
  77. package/dist/tool/index.d.ts +125 -21
  78. package/dist/tool/index.js +20 -13
  79. package/dist/{types-VQgymC1N.d.ts → types-Bj_J8u_W.d.ts} +44 -64
  80. package/dist/{types-CHiPh8U2.d.ts → types-C_LCeYNg.d.ts} +7 -7
  81. package/dist/types-RSCv7nQ4.d.ts +59 -0
  82. package/package.json +58 -53
  83. package/dist/builder-UpOWQMW3.d.ts +0 -34
  84. package/dist/chunk-7MUFEN4K.js +0 -559
  85. package/dist/chunk-7VKQ4WPB.js +0 -73
  86. package/dist/chunk-BFM2YHNM.js +0 -222
  87. package/dist/chunk-DWYX7ASF.js +0 -26
  88. package/dist/chunk-KUVSERLJ.js +0 -50
  89. package/dist/chunk-N7P4PN3O.js +0 -84
  90. package/dist/chunk-SDSBEQXG.js +0 -157
  91. package/dist/chunk-SQU2AJHO.js +0 -305
  92. package/dist/chunk-VBWWUHWI.js +0 -724
  93. package/dist/chunk-VEKUXUVF.js +0 -41
  94. package/dist/chunk-VNQBHPCT.js +0 -398
  95. package/dist/chunk-WWYYNWEW.js +0 -259
  96. package/dist/context/index.d.ts +0 -259
  97. package/dist/context/index.js +0 -26
  98. package/dist/events-CE72w8W4.d.ts +0 -149
  99. package/dist/host/index.d.ts +0 -45
  100. package/dist/host/index.js +0 -8
  101. package/dist/index-CWSchSql.d.ts +0 -1058
  102. package/dist/messages-BYWGn8TY.d.ts +0 -110
  103. package/dist/presets/index.d.ts +0 -53
  104. package/dist/presets/index.js +0 -28
  105. package/dist/reasoning/index.d.ts +0 -116
  106. package/dist/registry-DwYqsQkX.d.ts +0 -164
  107. package/dist/runner-e2YRcUoX.d.ts +0 -786
  108. package/dist/scope/index.d.ts +0 -10
  109. package/dist/scope/index.js +0 -14
  110. package/dist/session-manager-B_CWGTsl.d.ts +0 -274
  111. package/dist/signal/index.d.ts +0 -28
  112. package/dist/signal/index.js +0 -6
  113. package/dist/sub-agent/index.d.ts +0 -23
  114. package/dist/sub-agent/index.js +0 -15
  115. package/dist/tool-BHbyUAy3.d.ts +0 -150
  116. package/dist/tool-DLXAR9Ce.d.ts +0 -145
  117. package/dist/tracker-DClqYqTj.d.ts +0 -96
  118. package/dist/tracking/index.d.ts +0 -111
  119. package/dist/tracking/index.js +0 -20
  120. package/dist/types-BfNpU8NS.d.ts +0 -270
  121. package/dist/types-BnpEOYV-.d.ts +0 -50
  122. package/dist/types-CQL-SvTn.d.ts +0 -29
  123. package/dist/types-CWm-7rvB.d.ts +0 -55
  124. package/dist/types-KKDrdU9Y.d.ts +0 -325
  125. package/dist/types-QA4WhEfz.d.ts +0 -138
  126. package/dist/types-QKHHQLLq.d.ts +0 -336
  127. package/dist/types-YuWV4ag7.d.ts +0 -72
@@ -1,20 +1,31 @@
1
1
  import {
2
- DEFAULT_CONTEXT_LIMITS
3
- } from "./chunk-WWYYNWEW.js";
4
- import {
5
- Inference
6
- } from "./chunk-N6HWIEEA.js";
7
- import {
8
- executeAgentToolCall
9
- } from "./chunk-7VKQ4WPB.js";
2
+ Inference,
3
+ buildModelCallContext
4
+ } from "./chunk-CMYN2RCB.js";
10
5
  import {
6
+ PRUNE_PROTECTED_TOOLS,
7
+ accumulateUsage,
11
8
  currentScope,
9
+ executeAgentToolCall,
12
10
  snapshotScope,
13
11
  streamWithinScope,
14
12
  withinScope
15
- } from "./chunk-N7P4PN3O.js";
13
+ } from "./chunk-5NVVNXPQ.js";
14
+ import {
15
+ LLMError
16
+ } from "./chunk-N3VX7FEE.js";
17
+ import {
18
+ extractModelId,
19
+ extractProvider
20
+ } from "./chunk-I6PKJ7XQ.js";
21
+ import {
22
+ resolveCapability
23
+ } from "./chunk-FII65CN7.js";
24
+ import {
25
+ silentLogger
26
+ } from "./chunk-T4UIX5D7.js";
16
27
 
17
- // src/runtime/task/observer.ts
28
+ // src/execution/task/observer.ts
18
29
  function defaultAgentTaskCheckpointStrategy(input) {
19
30
  switch (input.event.type) {
20
31
  case "step-finish":
@@ -32,18 +43,20 @@ function defaultAgentTaskCheckpointStrategy(input) {
32
43
  }
33
44
  }
34
45
 
35
- // src/runtime/task/runner.ts
46
+ // src/execution/task/runner.ts
36
47
  import { randomUUID } from "crypto";
37
48
 
38
- // src/runtime/turn-state.ts
49
+ // src/execution/clone-usage.ts
50
+ function cloneUsage(usage) {
51
+ return { ...usage };
52
+ }
53
+
54
+ // src/execution/turn-state.ts
39
55
  var EMPTY_USAGE = {
40
56
  inputTokens: 0,
41
57
  outputTokens: 0,
42
58
  totalTokens: 0
43
59
  };
44
- function cloneUsage(usage) {
45
- return { ...usage };
46
- }
47
60
  function normalizeUsage(usage) {
48
61
  if (!usage) {
49
62
  return cloneUsage(EMPTY_USAGE);
@@ -58,13 +71,14 @@ function removeActiveToolCall(toolCalls, toolCallId) {
58
71
  return toolCalls.filter((toolCall) => toolCall.toolCallId !== toolCallId);
59
72
  }
60
73
  function createAgentTurnState(options) {
74
+ const restore = options.restoreFrom;
61
75
  return {
62
76
  sessionId: options.sessionId,
63
77
  phase: "initializing",
64
- step: 0,
65
- response: "",
66
- usage: cloneUsage(EMPTY_USAGE),
67
- eventCount: 0,
78
+ step: restore?.step ?? 0,
79
+ response: restore?.response ?? "",
80
+ usage: restore?.usage ? cloneUsage(restore.usage) : cloneUsage(EMPTY_USAGE),
81
+ eventCount: restore?.eventCount ?? 0,
68
82
  activeToolCalls: [],
69
83
  resolvedToolCalls: [],
70
84
  startedAt: options.startedAt,
@@ -77,7 +91,8 @@ function advanceAgentTurnState(state, event, updatedAt, options = {}) {
77
91
  usage: cloneUsage(state.usage),
78
92
  activeToolCalls: state.activeToolCalls.map((toolCall) => ({ ...toolCall })),
79
93
  resolvedToolCalls: state.resolvedToolCalls.map((toolCall) => ({
80
- ...toolCall
94
+ ...toolCall,
95
+ ...toolCall.metadata ? { metadata: structuredClone(toolCall.metadata) } : {}
81
96
  })),
82
97
  eventCount: state.eventCount + 1,
83
98
  lastEvent: event,
@@ -122,6 +137,7 @@ function advanceAgentTurnState(state, event, updatedAt, options = {}) {
122
137
  toolName: event.toolName,
123
138
  outcome: "result",
124
139
  value: event.result,
140
+ ...event.metadata ? { metadata: structuredClone(event.metadata) } : {},
125
141
  resolvedAt: updatedAt,
126
142
  ...options.toolReplayPolicy ? { replayPolicy: options.toolReplayPolicy } : {}
127
143
  }
@@ -195,7 +211,8 @@ function failAgentTurnState(state, error, updatedAt) {
195
211
  usage: cloneUsage(state.usage),
196
212
  activeToolCalls: state.activeToolCalls.map((toolCall) => ({ ...toolCall })),
197
213
  resolvedToolCalls: state.resolvedToolCalls.map((toolCall) => ({
198
- ...toolCall
214
+ ...toolCall,
215
+ ...toolCall.metadata ? { metadata: structuredClone(toolCall.metadata) } : {}
199
216
  })),
200
217
  phase: "failed",
201
218
  error: error.message,
@@ -203,7 +220,7 @@ function failAgentTurnState(state, error, updatedAt) {
203
220
  };
204
221
  }
205
222
 
206
- // src/runtime/task/runner.ts
223
+ // src/execution/task/runner.ts
207
224
  function normalizeNonEmpty(value, label) {
208
225
  const normalized = value.trim();
209
226
  if (!normalized) {
@@ -227,15 +244,14 @@ function buildRuntimeSessionId(prefix, key) {
227
244
  function nowIso() {
228
245
  return (/* @__PURE__ */ new Date()).toISOString();
229
246
  }
230
- async function notifyObservers(observers, invoke) {
247
+ async function notifyObservers(observers, invoke, logger) {
231
248
  for (const observer of observers) {
232
249
  try {
233
250
  await invoke(observer);
234
251
  } catch (error) {
235
- console.warn(
236
- "[agent-task-runner] observer error:",
237
- error instanceof Error ? error.message : String(error)
238
- );
252
+ logger.warn("observer error", {
253
+ error: error instanceof Error ? error.message : String(error)
254
+ });
239
255
  }
240
256
  }
241
257
  }
@@ -243,11 +259,13 @@ function createAgentTaskRunner(agent, options = {}) {
243
259
  const prefix = options.sessionPrefix ?? "runtime";
244
260
  const baseObservers = options.observers ?? [];
245
261
  const checkpointStrategy = options.checkpointStrategy ?? defaultAgentTaskCheckpointStrategy;
262
+ const log = options.logger?.child("task-runner") ?? silentLogger;
246
263
  return async (payload, context = {}) => {
247
264
  const message = normalizeNonEmpty(payload.message, "payload.message");
248
265
  const resolvedSessionId = payload.sessionId?.trim() || options.resolveSessionId?.(payload, context)?.trim() || buildRuntimeSessionId(prefix, context.fallbackSessionKey);
249
266
  const sessionId = normalizeNonEmpty(resolvedSessionId, "sessionId");
250
267
  const startedAt = nowIso();
268
+ const executionId = context.executionId?.trim() || `${sessionId}:${startedAt}`;
251
269
  return withinScope(
252
270
  {
253
271
  kind: "task",
@@ -263,13 +281,20 @@ function createAgentTaskRunner(agent, options = {}) {
263
281
  payload,
264
282
  context,
265
283
  sessionId,
266
- startedAt,
284
+ executionId,
285
+ startedAt: context.restoreFrom?.startedAt ?? startedAt,
267
286
  scope: snapshotScope()
268
287
  };
269
- const toolCalls = [];
288
+ const toolCalls = context.restoreFrom?.toolCalls.map((tc) => ({ ...tc })) ?? [];
270
289
  let turnState = createAgentTurnState({
271
290
  sessionId,
272
- startedAt
291
+ startedAt: context.restoreFrom?.startedAt ?? startedAt,
292
+ restoreFrom: context.restoreFrom ? {
293
+ response: context.restoreFrom.response,
294
+ usage: { ...context.restoreFrom.usage },
295
+ step: context.restoreFrom.step,
296
+ eventCount: context.restoreFrom.eventCount
297
+ } : void 0
273
298
  });
274
299
  const createSnapshot = () => ({
275
300
  sessionId,
@@ -298,13 +323,13 @@ function createAgentTaskRunner(agent, options = {}) {
298
323
  };
299
324
  await notifyObservers(baseObservers, async (observer) => {
300
325
  await observer.onCheckpoint?.(checkpoint);
301
- });
326
+ }, log);
302
327
  };
303
328
  await notifyObservers(baseObservers, async (observer) => {
304
329
  await observer.onTaskStart?.(run, createSnapshot());
305
- });
330
+ }, log);
306
331
  await emitCheckpoint("task-start");
307
- const activateCtx = baseObservers.find((o) => o.activateContext)?.activateContext?.bind(void 0, sessionId);
332
+ const activateCtx = baseObservers.find((o) => o.activateContext)?.activateContext?.bind(void 0, sessionId, executionId);
308
333
  try {
309
334
  const processChatStream = async () => {
310
335
  for await (const event of agent.chat(sessionId, message, {
@@ -318,7 +343,7 @@ function createAgentTaskRunner(agent, options = {}) {
318
343
  const snapshot = createSnapshot();
319
344
  await notifyObservers(baseObservers, async (observer) => {
320
345
  await observer.onTaskEvent?.(run, event, snapshot);
321
- });
346
+ }, log);
322
347
  const checkpointReason = checkpointStrategy({
323
348
  run,
324
349
  event,
@@ -345,14 +370,14 @@ function createAgentTaskRunner(agent, options = {}) {
345
370
  };
346
371
  await notifyObservers(baseObservers, async (observer) => {
347
372
  await observer.onTaskComplete?.(run, result, createSnapshot());
348
- });
373
+ }, log);
349
374
  return result;
350
375
  } catch (error) {
351
376
  const normalizedError = error instanceof Error ? error : new Error(String(error));
352
377
  turnState = failAgentTurnState(turnState, normalizedError, nowIso());
353
378
  await notifyObservers(baseObservers, async (observer) => {
354
379
  await observer.onTaskError?.(run, normalizedError, createSnapshot());
355
- });
380
+ }, log);
356
381
  await emitCheckpoint("task-error");
357
382
  throw normalizedError;
358
383
  }
@@ -361,50 +386,7 @@ function createAgentTaskRunner(agent, options = {}) {
361
386
  };
362
387
  }
363
388
 
364
- // src/runtime/model-messages.ts
365
- function convertAgentMessagesToModelMessages(messages) {
366
- return messages.flatMap((message) => {
367
- switch (message.role) {
368
- case "user":
369
- return [{ role: "user", content: message.content }];
370
- case "assistant": {
371
- if (message.toolCalls && message.toolCalls.length > 0) {
372
- const toolCallParts = message.toolCalls.map(
373
- (toolCall) => ({
374
- type: "tool-call",
375
- toolCallId: toolCall.toolCallId,
376
- toolName: toolCall.toolName,
377
- input: toolCall.args
378
- })
379
- );
380
- return [{ role: "assistant", content: toolCallParts }];
381
- }
382
- return [{ role: "assistant", content: message.content }];
383
- }
384
- case "tool":
385
- return [
386
- {
387
- role: "tool",
388
- content: [
389
- {
390
- type: "tool-result",
391
- toolCallId: message.toolCallId ?? "",
392
- toolName: message.toolName ?? "",
393
- output: {
394
- type: "text",
395
- value: typeof message.result === "string" ? message.result : JSON.stringify(message.result)
396
- }
397
- }
398
- ]
399
- }
400
- ];
401
- case "system":
402
- return [{ role: "system", content: message.content }];
403
- }
404
- });
405
- }
406
-
407
- // src/runtime/turn-engine/commit-batch.ts
389
+ // src/execution/turn-engine/commit-batch.ts
408
390
  import { randomUUID as randomUUID2 } from "crypto";
409
391
  function createAgentTurnBoundaryEvent(boundary, metadata = {}) {
410
392
  return {
@@ -433,13 +415,14 @@ function createAgentTurnStepCommitBatch(step, snapshot, options = {}) {
433
415
  createdAt
434
416
  };
435
417
  const toolMessages = snapshot.toolResults.map(
436
- ({ toolCallId, toolName, result }) => ({
418
+ ({ toolCallId, toolName, result, metadata }) => ({
437
419
  id: options.toolMessageIds?.[toolCallId] ?? randomUUID2(),
438
420
  role: "tool",
439
421
  content: typeof result === "string" ? result : JSON.stringify(result),
440
422
  toolCallId,
441
423
  toolName,
442
424
  result,
425
+ ...metadata ? { metadata } : {},
443
426
  createdAt
444
427
  })
445
428
  );
@@ -511,7 +494,7 @@ function createAgentTurnOutputCommit(options) {
511
494
  };
512
495
  }
513
496
 
514
- // src/runtime/turn-engine/engine.ts
497
+ // src/execution/turn-engine/engine.ts
515
498
  var AgentTurnEngine = class {
516
499
  turnState;
517
500
  pendingToolCalls = /* @__PURE__ */ new Map();
@@ -552,6 +535,7 @@ var AgentTurnEngine = class {
552
535
  toolCallId,
553
536
  toolName,
554
537
  result: resultData.result,
538
+ ...resultData.metadata ? { metadata: resultData.metadata } : {},
555
539
  ...replayPolicy ? { replayPolicy } : {}
556
540
  };
557
541
  }).filter((result) => result !== void 0)
@@ -574,6 +558,7 @@ var AgentTurnEngine = class {
574
558
  this.pendingToolResults.set(event.toolCallId, {
575
559
  toolName: event.toolName,
576
560
  result: event.result,
561
+ ...event.metadata ? { metadata: event.metadata } : {},
577
562
  replayPolicy: toolReplayPolicy
578
563
  });
579
564
  break;
@@ -616,7 +601,251 @@ function createAgentTurnEngine(options) {
616
601
  return new AgentTurnEngine(options);
617
602
  }
618
603
 
619
- // src/runtime/turn-runner/prepare.ts
604
+ // src/agent/context/estimation.ts
605
+ function estimateTokens(text) {
606
+ return Math.ceil(text.length / 4);
607
+ }
608
+ function estimateMessageTokens(message) {
609
+ if (typeof message.content === "string") {
610
+ return estimateTokens(message.content);
611
+ }
612
+ if (Array.isArray(message.content)) {
613
+ let total = 0;
614
+ for (const part of message.content) {
615
+ if (typeof part === "string") {
616
+ total += estimateTokens(part);
617
+ } else if ("text" in part && typeof part.text === "string") {
618
+ total += estimateTokens(part.text);
619
+ } else if ("type" in part && part.type === "image") {
620
+ total += 765;
621
+ }
622
+ }
623
+ return total;
624
+ }
625
+ return 0;
626
+ }
627
+ function estimateConversationTokens(messages) {
628
+ let total = 0;
629
+ for (const message of messages) {
630
+ total += estimateMessageTokens(message);
631
+ total += 4;
632
+ }
633
+ return total;
634
+ }
635
+
636
+ // src/agent/context/pruning.ts
637
+ var DEFAULT_CONTEXT_LIMITS = {
638
+ contextWindow: 128e3,
639
+ reserveTokens: 16e3,
640
+ // Reserve for output
641
+ protectedTokens: 4e4,
642
+ // Keep recent 40 k tokens
643
+ pruneMinimum: 2e4
644
+ // Don't prune until 20 k tokens
645
+ };
646
+ function getUsableTokenLimit(limits) {
647
+ return limits.contextWindow - limits.reserveTokens;
648
+ }
649
+ function isContextOverflowing(tokens, limits = DEFAULT_CONTEXT_LIMITS) {
650
+ return tokens > getUsableTokenLimit(limits);
651
+ }
652
+ function shouldPruneContext(tokens, limits = DEFAULT_CONTEXT_LIMITS) {
653
+ if (tokens < limits.pruneMinimum) return false;
654
+ return isContextOverflowing(tokens, limits);
655
+ }
656
+ function findCutPoint(messages, protectedTokens = DEFAULT_CONTEXT_LIMITS.protectedTokens) {
657
+ if (messages.length === 0) return 0;
658
+ let tokensFromEnd = 0;
659
+ let cutIndex = messages.length;
660
+ for (let i = messages.length - 1; i >= 0; i--) {
661
+ tokensFromEnd += estimateMessageTokens(messages[i]);
662
+ if (tokensFromEnd >= protectedTokens) {
663
+ cutIndex = i;
664
+ break;
665
+ }
666
+ }
667
+ if (cutIndex <= 1) return 0;
668
+ const startIndex = cutIndex >= messages.length ? messages.length - 1 : cutIndex;
669
+ for (let i = startIndex; i >= 1; i--) {
670
+ const msg = messages[i];
671
+ const prevMsg = messages[i - 1];
672
+ if (!msg || !prevMsg) continue;
673
+ if (msg.role === "tool") continue;
674
+ if (prevMsg.role === "assistant" || prevMsg.role === "user") {
675
+ return i;
676
+ }
677
+ }
678
+ return 0;
679
+ }
680
+ function pruneToolResults(messages, protectedTokens = DEFAULT_CONTEXT_LIMITS.protectedTokens, options) {
681
+ const protectedToolSet = /* @__PURE__ */ new Set([
682
+ ...PRUNE_PROTECTED_TOOLS,
683
+ ...options?.protectedTools ?? []
684
+ ]);
685
+ let tokensFromEnd = 0;
686
+ const tokenPositions = [];
687
+ for (let i = messages.length - 1; i >= 0; i--) {
688
+ tokensFromEnd += estimateMessageTokens(messages[i]);
689
+ tokenPositions[i] = tokensFromEnd;
690
+ }
691
+ return messages.map((msg, i) => {
692
+ if (tokenPositions[i] < protectedTokens) return msg;
693
+ if (!("role" in msg) || msg.role !== "tool") return msg;
694
+ const toolMsg = msg;
695
+ if ("compactedAt" in toolMsg && toolMsg.compactedAt) return msg;
696
+ if (toolMsg.toolName && protectedToolSet.has(toolMsg.toolName)) return msg;
697
+ const currentTokens = estimateTokens(toolMsg.content);
698
+ if (currentTokens < 500) return msg;
699
+ return {
700
+ ...toolMsg,
701
+ content: `[Output pruned - was ${currentTokens} tokens]`,
702
+ compactedAt: Date.now()
703
+ };
704
+ });
705
+ }
706
+
707
+ // src/agent/context/summarization.ts
708
+ import { generateText } from "ai";
709
+ var DEFAULT_SUMMARY_PROMPT = `You are summarizing a conversation to continue it with context.
710
+
711
+ Create a structured summary that captures:
712
+ 1. **Goal**: What the user is trying to accomplish
713
+ 2. **Progress**: What has been done so far
714
+ 3. **Decisions**: Key decisions made during the conversation
715
+ 4. **Current State**: Where we left off
716
+ 5. **Next Steps**: What should happen next
717
+
718
+ Be concise but comprehensive. Include specific file paths, function names, and technical details that would be lost otherwise.
719
+
720
+ Format as a clear summary that could be given to another assistant to continue the work.`;
721
+ async function generateSummary(messages, options) {
722
+ const conversationText = messages.map((m) => {
723
+ const role = m.role.toUpperCase();
724
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
725
+ return `[${role}]: ${content}`;
726
+ }).join("\n\n");
727
+ const prompt = options.customPrompt || DEFAULT_SUMMARY_PROMPT;
728
+ const { text } = await generateText({
729
+ model: options.model,
730
+ maxOutputTokens: options.maxTokens ?? 2e3,
731
+ system: prompt,
732
+ prompt: `Summarize this conversation:
733
+
734
+ ${conversationText}`
735
+ });
736
+ return text;
737
+ }
738
+ async function pruneContext(messages, options = {}) {
739
+ const limits = options.limits ?? DEFAULT_CONTEXT_LIMITS;
740
+ let currentMessages = [...messages];
741
+ let tokensRemoved = 0;
742
+ let removedCount = 0;
743
+ let summarized = false;
744
+ let summary;
745
+ const initialTokens = estimateConversationTokens(currentMessages);
746
+ if (!shouldPruneContext(initialTokens, limits)) {
747
+ return { messages: currentMessages, removedCount: 0, tokensRemoved: 0, summarized: false };
748
+ }
749
+ const prunedMessages = pruneToolResults(currentMessages, limits.protectedTokens);
750
+ const afterPruneTokens = estimateConversationTokens(prunedMessages);
751
+ tokensRemoved = initialTokens - afterPruneTokens;
752
+ currentMessages = prunedMessages;
753
+ if (!isContextOverflowing(afterPruneTokens, limits)) {
754
+ return { messages: currentMessages, removedCount: 0, tokensRemoved, summarized: false };
755
+ }
756
+ const cutIndex = findCutPoint(currentMessages, limits.protectedTokens);
757
+ if (cutIndex === 0) {
758
+ return { messages: currentMessages, removedCount: 0, tokensRemoved, summarized: false };
759
+ }
760
+ const toSummarize = currentMessages.slice(0, cutIndex);
761
+ const toKeep = currentMessages.slice(cutIndex);
762
+ removedCount = toSummarize.length;
763
+ tokensRemoved += estimateConversationTokens(toSummarize);
764
+ if (options.model) {
765
+ summary = await generateSummary(toSummarize, {
766
+ model: options.model,
767
+ customPrompt: options.summaryPrompt
768
+ });
769
+ summarized = true;
770
+ const summaryMessage = {
771
+ id: crypto.randomUUID(),
772
+ role: "system",
773
+ content: `## Previous Conversation Summary
774
+
775
+ ${summary}`,
776
+ createdAt: /* @__PURE__ */ new Date()
777
+ };
778
+ currentMessages = [summaryMessage, ...toKeep];
779
+ } else {
780
+ currentMessages = toKeep;
781
+ }
782
+ return { messages: currentMessages, removedCount, tokensRemoved, summarized, summary };
783
+ }
784
+
785
+ // src/agent/context/manager.ts
786
+ var ContextManager = class {
787
+ limits;
788
+ model;
789
+ summaryPrompt;
790
+ constructor(options) {
791
+ this.limits = { ...DEFAULT_CONTEXT_LIMITS, ...options?.limits };
792
+ this.model = options?.model;
793
+ this.summaryPrompt = options?.summaryPrompt;
794
+ }
795
+ /** Get a copy of the current context limits. */
796
+ getLimits() {
797
+ return { ...this.limits };
798
+ }
799
+ /** Update context limits (e.g. when switching models). */
800
+ setLimits(limits) {
801
+ this.limits = { ...this.limits, ...limits };
802
+ }
803
+ /** Set the model used for summarisation. */
804
+ setModel(model) {
805
+ this.model = model;
806
+ }
807
+ /** Estimate total tokens for a message array. */
808
+ estimateTokens(messages) {
809
+ return estimateConversationTokens(messages);
810
+ }
811
+ /** Check whether the context is overflowing. */
812
+ isOverflowing(messages) {
813
+ const tokens = this.estimateTokens(messages);
814
+ return isContextOverflowing(tokens, this.limits);
815
+ }
816
+ /** Check whether pruning should be triggered. */
817
+ shouldPrune(messages) {
818
+ const tokens = this.estimateTokens(messages);
819
+ return shouldPruneContext(tokens, this.limits);
820
+ }
821
+ /** Prune context to fit within limits. */
822
+ async prune(messages) {
823
+ return pruneContext(messages, {
824
+ model: this.model,
825
+ limits: this.limits,
826
+ summaryPrompt: this.summaryPrompt
827
+ });
828
+ }
829
+ /**
830
+ * Get a snapshot of token statistics.
831
+ *
832
+ * Useful for dashboards, logging, or deciding whether to prune.
833
+ */
834
+ getStats(messages) {
835
+ const tokens = this.estimateTokens(messages);
836
+ const limit = getUsableTokenLimit(this.limits);
837
+ return {
838
+ tokens,
839
+ limit,
840
+ available: Math.max(0, limit - tokens),
841
+ utilizationPercent: Math.round(tokens / limit * 100),
842
+ isOverflowing: isContextOverflowing(tokens, this.limits),
843
+ shouldPrune: shouldPruneContext(tokens, this.limits)
844
+ };
845
+ }
846
+ };
847
+
848
+ // src/execution/turn-runner/prepare.ts
620
849
  function prepareModelStep(options) {
621
850
  const modelMessages = Array.from(options.toModelMessages(options.messages));
622
851
  return {
@@ -625,6 +854,7 @@ function prepareModelStep(options) {
625
854
  modelMessages,
626
855
  inferenceInput: {
627
856
  sessionID: options.sessionId,
857
+ turnID: options.turnTracker?.getCurrentTurnId?.() ?? void 0,
628
858
  step: options.step,
629
859
  model: options.config.model,
630
860
  system: options.systemPrompts,
@@ -634,6 +864,7 @@ function prepareModelStep(options) {
634
864
  mcpTools: options.mcpTools,
635
865
  cwd: options.config.cwd,
636
866
  host: options.host,
867
+ humanInputController: options.humanInputController,
637
868
  temperature: options.config.temperature,
638
869
  topP: options.config.topP,
639
870
  maxOutputTokens: options.config.maxOutputTokens,
@@ -651,12 +882,15 @@ function prepareModelStep(options) {
651
882
  doomLoopThreshold: options.config.doomLoopThreshold,
652
883
  enforceDoomLoop: options.config.enforceDoomLoop,
653
884
  onDoomLoop: options.config.onDoomLoop,
654
- contextTokenLimit: options.config.contextWindow ? options.config.contextWindow - DEFAULT_CONTEXT_LIMITS.reserveTokens : void 0
885
+ contextTokenLimit: options.config.contextWindow ? getUsableTokenLimit({
886
+ contextWindow: options.config.contextWindow,
887
+ reserveTokens: options.config.reserveTokens ?? DEFAULT_CONTEXT_LIMITS.reserveTokens
888
+ }) : void 0
655
889
  }
656
890
  };
657
891
  }
658
892
 
659
- // src/runtime/step-processing/doom-loop.ts
893
+ // src/execution/step-processing/doom-loop.ts
660
894
  var DEFAULT_DOOM_LOOP_THRESHOLD = 3;
661
895
  var DoomLoopError = class extends Error {
662
896
  toolName;
@@ -733,7 +967,7 @@ async function recordToolCallAndCheckDoomLoop(options) {
733
967
  options.warn?.(`[StepProcessing] ${doomError.message}`);
734
968
  }
735
969
 
736
- // src/runtime/step-processing/overflow.ts
970
+ // src/execution/step-processing/overflow.ts
737
971
  var ContextOverflowError = class extends Error {
738
972
  inputTokens;
739
973
  limit;
@@ -760,11 +994,23 @@ async function handleContextOverflow(options) {
760
994
  }
761
995
  }
762
996
 
763
- // src/runtime/step-processing/process.ts
997
+ // src/execution/step-processing/process.ts
998
+ function normalizeToolResultChunk(output) {
999
+ if (output && typeof output === "object" && "__cuylabsAgentToolResult" in output && output.__cuylabsAgentToolResult === true) {
1000
+ const structured = output;
1001
+ return {
1002
+ result: structured.output,
1003
+ ...structured.metadata ? { metadata: structured.metadata } : {}
1004
+ };
1005
+ }
1006
+ return { result: output };
1007
+ }
764
1008
  async function processStepStream(stream, options) {
765
1009
  const { abort, onEvent } = options;
1010
+ const normalizeError = options.normalizeError ?? ((error2) => error2 instanceof Error ? error2 : new Error(String(error2)));
766
1011
  const doomLoopThreshold = options.doomLoopThreshold ?? DEFAULT_DOOM_LOOP_THRESHOLD;
767
1012
  const maxSteps = options.maxSteps ?? 50;
1013
+ const log = options.logger;
768
1014
  let stepCount = options.currentStep ?? 1;
769
1015
  let sawStartStep = false;
770
1016
  let text = "";
@@ -779,10 +1025,7 @@ async function processStepStream(stream, options) {
779
1025
  try {
780
1026
  for await (const rawChunk of stream.fullStream) {
781
1027
  const chunk = rawChunk;
782
- if (process.env.DEBUG_PROCESSOR) {
783
- process.stderr.write(`[step-processing] Chunk received: ${chunk.type}
784
- `);
785
- }
1028
+ log?.debug(`Chunk received: ${chunk.type}`);
786
1029
  abort.throwIfAborted();
787
1030
  switch (chunk.type) {
788
1031
  case "start-step":
@@ -838,19 +1081,23 @@ async function processStepStream(stream, options) {
838
1081
  `)
839
1082
  });
840
1083
  break;
841
- case "tool-result":
1084
+ case "tool-result": {
1085
+ const normalized = normalizeToolResultChunk(chunk.output);
842
1086
  toolResults.push({
843
1087
  toolName: chunk.toolName,
844
1088
  toolCallId: chunk.toolCallId,
845
- result: chunk.output
1089
+ result: normalized.result,
1090
+ ...normalized.metadata ? { metadata: normalized.metadata } : {}
846
1091
  });
847
1092
  await onEvent({
848
1093
  type: "tool-result",
849
1094
  toolName: chunk.toolName,
850
1095
  toolCallId: chunk.toolCallId,
851
- result: chunk.output
1096
+ result: normalized.result,
1097
+ ...normalized.metadata ? { metadata: normalized.metadata } : {}
852
1098
  });
853
1099
  break;
1100
+ }
854
1101
  case "tool-error":
855
1102
  await onEvent({
856
1103
  type: "tool-error",
@@ -914,7 +1161,7 @@ async function processStepStream(stream, options) {
914
1161
  }
915
1162
  }
916
1163
  } catch (caught) {
917
- error = caught instanceof Error ? caught : new Error(String(caught));
1164
+ error = normalizeError(caught);
918
1165
  await onEvent({ type: "status", status: "error" });
919
1166
  await onEvent({ type: "error", error });
920
1167
  }
@@ -934,21 +1181,22 @@ async function processStepStream(stream, options) {
934
1181
  error
935
1182
  };
936
1183
  }
937
- var processStream = processStepStream;
938
1184
 
939
- // src/runtime/turn-runner/stream-step.ts
940
- function buildModelCallContext(options) {
1185
+ // src/execution/turn-runner/stream-step.ts
1186
+ function buildStepModelCallContext(options) {
941
1187
  const input = options.preparedStep.inferenceInput;
942
- return {
943
- sessionID: input.sessionID,
944
- step: input.step ?? options.preparedStep.step,
945
- cwd: input.cwd,
946
- abort: input.abort,
947
- model: input.model,
948
- toolNames: Object.keys(input.tools),
949
- mcpToolNames: Object.keys(input.mcpTools ?? {}),
950
- scope: snapshotScope()
951
- };
1188
+ const ctx = buildModelCallContext(input);
1189
+ if (input.step == null) {
1190
+ ctx.step = options.preparedStep.step;
1191
+ }
1192
+ return ctx;
1193
+ }
1194
+ function normalizeModelStepError(options, error) {
1195
+ const model = options.preparedStep.inferenceInput.model;
1196
+ return LLMError.from(error, {
1197
+ provider: extractProvider(model),
1198
+ model: extractModelId(model)
1199
+ });
952
1200
  }
953
1201
  async function* runModelStep(options) {
954
1202
  return yield* streamWithinScope(
@@ -960,13 +1208,20 @@ async function* runModelStep(options) {
960
1208
  },
961
1209
  (async function* () {
962
1210
  const { preparedStep, turnEngine, applyCommitBatch } = options;
963
- const stream = await Inference.streamStep(preparedStep.inferenceInput);
964
1211
  const eventQueue = [];
965
1212
  let resolveNext = null;
966
1213
  let streamDone = false;
967
1214
  let streamError;
968
1215
  const intervention = preparedStep.inferenceInput.intervention;
969
1216
  const middleware = preparedStep.inferenceInput.middleware;
1217
+ const emitQueuedEvent = async (event) => {
1218
+ middleware?.emitEvent(event);
1219
+ eventQueue.push(event);
1220
+ if (resolveNext) {
1221
+ resolveNext();
1222
+ resolveNext = null;
1223
+ }
1224
+ };
970
1225
  if (intervention) {
971
1226
  intervention.onApplied = (item) => {
972
1227
  eventQueue.push({
@@ -979,10 +1234,26 @@ async function* runModelStep(options) {
979
1234
  resolveNext = null;
980
1235
  }
981
1236
  };
1237
+ intervention.onDeferredQueued = (item) => {
1238
+ eventQueue.push({
1239
+ type: "follow-up-queued",
1240
+ id: item.id,
1241
+ message: item.message
1242
+ });
1243
+ if (resolveNext) {
1244
+ resolveNext();
1245
+ resolveNext = null;
1246
+ }
1247
+ };
982
1248
  }
1249
+ const stream = await Inference.streamStep({
1250
+ ...preparedStep.inferenceInput,
1251
+ onEvent: emitQueuedEvent
1252
+ });
983
1253
  const processPromise = processStepStream(stream, {
984
1254
  sessionID: preparedStep.inferenceInput.sessionID,
985
1255
  abort: preparedStep.inferenceInput.abort,
1256
+ normalizeError: (error) => normalizeModelStepError(options, error),
986
1257
  currentStep: preparedStep.step,
987
1258
  maxSteps: preparedStep.stepProcessing.maxSteps,
988
1259
  doomLoopThreshold: preparedStep.stepProcessing.doomLoopThreshold ?? 3,
@@ -992,14 +1263,7 @@ async function* runModelStep(options) {
992
1263
  contextTokenLimit: preparedStep.stepProcessing.contextTokenLimit,
993
1264
  onContextOverflow: async () => {
994
1265
  },
995
- onEvent: async (event) => {
996
- middleware?.emitEvent(event);
997
- eventQueue.push(event);
998
- if (resolveNext) {
999
- resolveNext();
1000
- resolveNext = null;
1001
- }
1002
- }
1266
+ onEvent: emitQueuedEvent
1003
1267
  }).then((result2) => {
1004
1268
  streamDone = true;
1005
1269
  if (resolveNext) {
@@ -1052,7 +1316,7 @@ async function* runModelStep(options) {
1052
1316
  usage: result.usage,
1053
1317
  finishReason: result.finishReason
1054
1318
  },
1055
- buildModelCallContext(options)
1319
+ buildStepModelCallContext(options)
1056
1320
  );
1057
1321
  result.text = revised.text;
1058
1322
  result.usage = revised.usage;
@@ -1063,8 +1327,7 @@ async function* runModelStep(options) {
1063
1327
  );
1064
1328
  }
1065
1329
 
1066
- // src/runtime/turn-runner/tool-batch.ts
1067
- import { randomUUID as randomUUID3 } from "crypto";
1330
+ // src/execution/turn-runner/tool-batch.ts
1068
1331
  function cloneToolResultRecord(options) {
1069
1332
  return new Map(
1070
1333
  options.snapshot.toolResults.map((toolResult) => [
@@ -1073,105 +1336,164 @@ function cloneToolResultRecord(options) {
1073
1336
  ])
1074
1337
  );
1075
1338
  }
1076
- function createToolBatchErrorResult(options) {
1077
- return {
1078
- type: "tool-error",
1079
- toolName: options.toolName,
1080
- toolCallId: options.toolCallId,
1081
- error: options.errorMessage
1082
- };
1339
+ function recordToolResult(toolCall, event, result, accumulators, metadata) {
1340
+ accumulators.events.push(event);
1341
+ accumulators.toolResultsByCallId.set(toolCall.toolCallId, {
1342
+ toolCallId: toolCall.toolCallId,
1343
+ toolName: toolCall.toolName,
1344
+ result,
1345
+ ...metadata ? { metadata } : {},
1346
+ ...toolCall.replayPolicy ? { replayPolicy: toolCall.replayPolicy } : {}
1347
+ });
1348
+ if (accumulators.turnState.value) {
1349
+ accumulators.turnState.value = advanceAgentTurnState(
1350
+ accumulators.turnState.value,
1351
+ event,
1352
+ (/* @__PURE__ */ new Date()).toISOString(),
1353
+ {
1354
+ ...toolCall.replayPolicy ? { toolReplayPolicy: toolCall.replayPolicy } : {}
1355
+ }
1356
+ );
1357
+ }
1083
1358
  }
1084
- function createToolBatchResultEvent(options) {
1085
- return {
1086
- type: "tool-result",
1087
- toolName: options.toolName,
1088
- toolCallId: options.toolCallId,
1089
- result: options.result
1090
- };
1359
+ async function isParallelSafe(tool, args, cwd) {
1360
+ const initialized = await tool.init({ cwd });
1361
+ const cap = initialized.capabilities?.parallelSafe;
1362
+ const safe = resolveCapability(
1363
+ cap,
1364
+ args,
1365
+ /* fallback */
1366
+ false
1367
+ );
1368
+ return { safe, initialized };
1369
+ }
1370
+ async function executeBatchEntry(toolCall, options, accumulators, preInitialized) {
1371
+ const tool = options.tools[toolCall.toolName];
1372
+ if (!tool) {
1373
+ const errorMessage = `Tool '${toolCall.toolName}' is not registered`;
1374
+ recordToolResult(
1375
+ toolCall,
1376
+ {
1377
+ type: "tool-error",
1378
+ toolName: toolCall.toolName,
1379
+ toolCallId: toolCall.toolCallId,
1380
+ error: errorMessage
1381
+ },
1382
+ `Error: ${errorMessage}`,
1383
+ accumulators
1384
+ );
1385
+ return;
1386
+ }
1387
+ try {
1388
+ const executed = await executeAgentToolCall({
1389
+ toolName: toolCall.toolName,
1390
+ tool,
1391
+ toolCallId: toolCall.toolCallId,
1392
+ params: toolCall.args,
1393
+ cwd: options.cwd,
1394
+ abort: options.abort,
1395
+ sessionID: options.sessionId,
1396
+ turnID: options.turnTracker?.getCurrentTurnId?.() ?? void 0,
1397
+ messageID: toolCall.toolCallId,
1398
+ ...options.host ? { host: options.host } : {},
1399
+ ...options.humanInputController ? { humanInputController: options.humanInputController } : {},
1400
+ ...options.turnTracker ? { turnTracker: options.turnTracker } : {},
1401
+ ...options.middleware ? { middleware: options.middleware } : {},
1402
+ onEvent: async (event) => {
1403
+ accumulators.events.push(event);
1404
+ },
1405
+ ...preInitialized ? { initialized: preInitialized } : {}
1406
+ });
1407
+ recordToolResult(
1408
+ toolCall,
1409
+ {
1410
+ type: "tool-result",
1411
+ toolName: toolCall.toolName,
1412
+ toolCallId: toolCall.toolCallId,
1413
+ result: executed.output,
1414
+ ...executed.metadata ? { metadata: executed.metadata } : {}
1415
+ },
1416
+ executed.output,
1417
+ accumulators,
1418
+ executed.metadata
1419
+ );
1420
+ } catch (error) {
1421
+ const errorMessage = error instanceof Error ? error.message : String(error);
1422
+ recordToolResult(
1423
+ toolCall,
1424
+ {
1425
+ type: "tool-error",
1426
+ toolName: toolCall.toolName,
1427
+ toolCallId: toolCall.toolCallId,
1428
+ error: errorMessage
1429
+ },
1430
+ `Error: ${errorMessage}`,
1431
+ accumulators
1432
+ );
1433
+ }
1091
1434
  }
1092
1435
  async function runToolBatch(options) {
1093
1436
  const toolResultsByCallId = cloneToolResultRecord(options);
1094
1437
  const events = [];
1095
- let turnState = options.turnState ? structuredClone(options.turnState) : void 0;
1096
- for (const toolCall of options.snapshot.toolCalls) {
1097
- if (toolResultsByCallId.has(toolCall.toolCallId)) {
1098
- continue;
1099
- }
1438
+ const turnState = {
1439
+ value: options.turnState ? structuredClone(options.turnState) : void 0
1440
+ };
1441
+ const accumulators = { events, toolResultsByCallId, turnState };
1442
+ const pending = options.snapshot.toolCalls.filter(
1443
+ (tc) => !toolResultsByCallId.has(tc.toolCallId)
1444
+ );
1445
+ const parallelGroup = [];
1446
+ const serialGroup = [];
1447
+ for (const toolCall of pending) {
1100
1448
  const tool = options.tools[toolCall.toolName];
1101
- const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1102
1449
  if (!tool) {
1103
- const errorMessage = `Tool '${toolCall.toolName}' is not registered`;
1104
- const event = createToolBatchErrorResult({
1105
- toolCallId: toolCall.toolCallId,
1106
- toolName: toolCall.toolName,
1107
- errorMessage,
1108
- replayPolicy: toolCall.replayPolicy
1109
- });
1110
- events.push(event);
1111
- toolResultsByCallId.set(toolCall.toolCallId, {
1112
- toolCallId: toolCall.toolCallId,
1113
- toolName: toolCall.toolName,
1114
- result: `Error: ${errorMessage}`,
1115
- ...toolCall.replayPolicy ? { replayPolicy: toolCall.replayPolicy } : {}
1116
- });
1117
- if (turnState) {
1118
- turnState = advanceAgentTurnState(turnState, event, updatedAt, {
1119
- ...toolCall.replayPolicy ? { toolReplayPolicy: toolCall.replayPolicy } : {}
1120
- });
1121
- }
1450
+ serialGroup.push({ toolCall });
1122
1451
  continue;
1123
1452
  }
1453
+ let safe = false;
1454
+ let initialized;
1124
1455
  try {
1125
- const executed = await executeAgentToolCall({
1126
- toolName: toolCall.toolName,
1456
+ const result = await isParallelSafe(
1127
1457
  tool,
1128
- params: toolCall.args,
1129
- cwd: options.cwd,
1130
- abort: options.abort,
1131
- sessionID: options.sessionId,
1132
- messageID: randomUUID3(),
1133
- ...options.host ? { host: options.host } : {},
1134
- ...options.turnTracker ? { turnTracker: options.turnTracker } : {},
1135
- ...options.middleware ? { middleware: options.middleware } : {}
1136
- });
1137
- const event = createToolBatchResultEvent({
1138
- toolCallId: toolCall.toolCallId,
1139
- toolName: toolCall.toolName,
1140
- result: executed.output
1141
- });
1142
- events.push(event);
1143
- toolResultsByCallId.set(toolCall.toolCallId, {
1144
- toolCallId: toolCall.toolCallId,
1145
- toolName: toolCall.toolName,
1146
- result: executed.output,
1147
- ...toolCall.replayPolicy ? { replayPolicy: toolCall.replayPolicy } : {}
1148
- });
1149
- if (turnState) {
1150
- turnState = advanceAgentTurnState(turnState, event, updatedAt, {
1151
- ...toolCall.replayPolicy ? { toolReplayPolicy: toolCall.replayPolicy } : {}
1152
- });
1153
- }
1154
- } catch (error) {
1155
- const errorMessage = error instanceof Error ? error.message : String(error);
1156
- const event = createToolBatchErrorResult({
1157
- toolCallId: toolCall.toolCallId,
1158
- toolName: toolCall.toolName,
1159
- errorMessage,
1160
- replayPolicy: toolCall.replayPolicy
1161
- });
1162
- events.push(event);
1163
- toolResultsByCallId.set(toolCall.toolCallId, {
1164
- toolCallId: toolCall.toolCallId,
1165
- toolName: toolCall.toolName,
1166
- result: `Error: ${errorMessage}`,
1167
- ...toolCall.replayPolicy ? { replayPolicy: toolCall.replayPolicy } : {}
1168
- });
1169
- if (turnState) {
1170
- turnState = advanceAgentTurnState(turnState, event, updatedAt, {
1171
- ...toolCall.replayPolicy ? { toolReplayPolicy: toolCall.replayPolicy } : {}
1172
- });
1458
+ toolCall.args,
1459
+ options.cwd
1460
+ );
1461
+ safe = result.safe;
1462
+ initialized = result.initialized;
1463
+ } catch {
1464
+ }
1465
+ if (safe && initialized) {
1466
+ parallelGroup.push({ toolCall, initialized });
1467
+ } else {
1468
+ serialGroup.push({ toolCall, initialized });
1469
+ }
1470
+ }
1471
+ if (parallelGroup.length > 0) {
1472
+ await Promise.all(
1473
+ parallelGroup.map(
1474
+ ({ toolCall, initialized }) => executeBatchEntry(toolCall, options, accumulators, initialized)
1475
+ )
1476
+ );
1477
+ }
1478
+ for (const { toolCall, initialized } of serialGroup) {
1479
+ if (options.intervention?.hasPending && initialized) {
1480
+ const interruptPolicy = initialized.capabilities?.onInterrupt ?? "finish";
1481
+ if (interruptPolicy === "cancel") {
1482
+ recordToolResult(
1483
+ toolCall,
1484
+ {
1485
+ type: "tool-error",
1486
+ toolName: toolCall.toolName,
1487
+ toolCallId: toolCall.toolCallId,
1488
+ error: "Tool cancelled: user sent a new message"
1489
+ },
1490
+ "Error: Tool cancelled: user sent a new message",
1491
+ accumulators
1492
+ );
1493
+ continue;
1173
1494
  }
1174
1495
  }
1496
+ await executeBatchEntry(toolCall, options, accumulators, initialized);
1175
1497
  }
1176
1498
  return {
1177
1499
  snapshot: {
@@ -1180,12 +1502,12 @@ async function runToolBatch(options) {
1180
1502
  ),
1181
1503
  toolResults: options.snapshot.toolCalls.map((toolCall) => toolResultsByCallId.get(toolCall.toolCallId)).filter((toolResult) => toolResult !== void 0)
1182
1504
  },
1183
- ...turnState ? { turnState } : {},
1505
+ ...turnState.value ? { turnState: turnState.value } : {},
1184
1506
  events
1185
1507
  };
1186
1508
  }
1187
1509
 
1188
- // src/runtime/turn-runner/commit.ts
1510
+ // src/execution/turn-runner/commit.ts
1189
1511
  async function* commitStep(options) {
1190
1512
  return yield* streamWithinScope(
1191
1513
  {
@@ -1226,10 +1548,7 @@ async function* commitOutput(options) {
1226
1548
  );
1227
1549
  }
1228
1550
 
1229
- // src/runtime/workflow-state.ts
1230
- function cloneUsage2(usage) {
1231
- return usage ? { ...usage } : void 0;
1232
- }
1551
+ // src/execution/workflow-state.ts
1233
1552
  function cloneMessageSnapshot(message) {
1234
1553
  return {
1235
1554
  ...message,
@@ -1241,9 +1560,96 @@ function cloneMessageSnapshot(message) {
1241
1560
  } : {},
1242
1561
  ...message.role === "assistant" && message.error ? {
1243
1562
  error: { ...message.error }
1563
+ } : {},
1564
+ ...message.role === "tool" && message.metadata ? {
1565
+ metadata: structuredClone(message.metadata)
1566
+ } : {}
1567
+ };
1568
+ }
1569
+ function createAgentWorkflowTurnState(options) {
1570
+ const pendingInput = options.initialInput ? cloneMessageSnapshot(options.initialInput) : void 0;
1571
+ return {
1572
+ sessionId: options.sessionId,
1573
+ systemPrompts: [...options.systemPrompts ?? []],
1574
+ messages: (options.initialMessages ?? []).map(cloneMessageSnapshot),
1575
+ phase: pendingInput ? "input-commit" : "model-step",
1576
+ ...pendingInput ? { pendingInput } : {},
1577
+ step: options.initialStep ?? 1,
1578
+ maxSteps: options.maxSteps,
1579
+ replayDecisions: {},
1580
+ startedAt: options.startedAt,
1581
+ updatedAt: options.startedAt
1582
+ };
1583
+ }
1584
+ function cloneAgentWorkflowTurnState(state) {
1585
+ return {
1586
+ ...state,
1587
+ systemPrompts: [...state.systemPrompts],
1588
+ messages: state.messages.map(cloneMessageSnapshot),
1589
+ ...state.pendingInput ? { pendingInput: cloneMessageSnapshot(state.pendingInput) } : {},
1590
+ ...state.usage ? { usage: cloneUsage(state.usage) } : {},
1591
+ ...state.turnState ? { turnState: structuredClone(state.turnState) } : {},
1592
+ ...state.lastModelStep ? { lastModelStep: structuredClone(state.lastModelStep) } : {},
1593
+ replayDecisions: Object.fromEntries(
1594
+ Object.entries(state.replayDecisions).map(([toolCallId, decision]) => [
1595
+ toolCallId,
1596
+ { ...decision }
1597
+ ])
1598
+ ),
1599
+ ...state.pendingInterventions?.length ? {
1600
+ pendingInterventions: state.pendingInterventions.map(
1601
+ (intervention) => ({ ...intervention })
1602
+ )
1603
+ } : {},
1604
+ ...state.queuedFollowUps?.length ? {
1605
+ queuedFollowUps: state.queuedFollowUps.map((intervention) => ({
1606
+ ...intervention
1607
+ }))
1244
1608
  } : {}
1245
1609
  };
1246
1610
  }
1611
+ function applyWorkflowInterventions(state, interventions, updatedAt) {
1612
+ if (interventions.length === 0) {
1613
+ return state;
1614
+ }
1615
+ const injected = interventions.map((intervention) => ({
1616
+ id: intervention.id,
1617
+ role: "user",
1618
+ createdAt: intervention.createdAt,
1619
+ content: intervention.message
1620
+ }));
1621
+ return {
1622
+ ...state,
1623
+ messages: [...state.messages, ...injected],
1624
+ pendingInterventions: void 0,
1625
+ updatedAt
1626
+ };
1627
+ }
1628
+ function drainWorkflowInterventions(state, updatedAt) {
1629
+ if (!state.pendingInterventions?.length) {
1630
+ return state;
1631
+ }
1632
+ return applyWorkflowInterventions(
1633
+ state,
1634
+ state.pendingInterventions,
1635
+ updatedAt
1636
+ );
1637
+ }
1638
+ function queueWorkflowFollowUps(state, followUps, updatedAt) {
1639
+ if (followUps.length === 0) {
1640
+ return state;
1641
+ }
1642
+ return {
1643
+ ...state,
1644
+ queuedFollowUps: [
1645
+ ...state.queuedFollowUps ?? [],
1646
+ ...followUps.map((followUp) => ({ ...followUp }))
1647
+ ],
1648
+ updatedAt
1649
+ };
1650
+ }
1651
+
1652
+ // src/execution/workflow-snapshot.ts
1247
1653
  function snapshotAgentWorkflowMessage(message) {
1248
1654
  switch (message.role) {
1249
1655
  case "user":
@@ -1281,6 +1687,7 @@ function snapshotAgentWorkflowMessage(message) {
1281
1687
  toolCallId: message.toolCallId,
1282
1688
  toolName: message.toolName,
1283
1689
  result: message.result,
1690
+ ...message.metadata ? { metadata: structuredClone(message.metadata) } : {},
1284
1691
  ...message.compactedAt !== void 0 ? { compactedAt: message.compactedAt } : {}
1285
1692
  };
1286
1693
  case "system":
@@ -1293,7 +1700,7 @@ function snapshotAgentWorkflowMessage(message) {
1293
1700
  }
1294
1701
  }
1295
1702
  function snapshotAgentWorkflowMessages(messages) {
1296
- return messages.map(snapshotAgentWorkflowMessage);
1703
+ return messages.map((message) => snapshotAgentWorkflowMessage(message));
1297
1704
  }
1298
1705
  function restoreAgentWorkflowMessage(snapshot) {
1299
1706
  const createdAt = new Date(snapshot.createdAt);
@@ -1333,6 +1740,7 @@ function restoreAgentWorkflowMessage(snapshot) {
1333
1740
  toolCallId: snapshot.toolCallId,
1334
1741
  toolName: snapshot.toolName,
1335
1742
  result: snapshot.result,
1743
+ ...snapshot.metadata ? { metadata: structuredClone(snapshot.metadata) } : {},
1336
1744
  ...snapshot.compactedAt !== void 0 ? { compactedAt: snapshot.compactedAt } : {}
1337
1745
  };
1338
1746
  case "system":
@@ -1347,52 +1755,8 @@ function restoreAgentWorkflowMessage(snapshot) {
1347
1755
  function restoreAgentWorkflowMessages(snapshots) {
1348
1756
  return snapshots.map(restoreAgentWorkflowMessage);
1349
1757
  }
1350
- function createAgentWorkflowTurnState(options) {
1351
- return {
1352
- sessionId: options.sessionId,
1353
- systemPrompts: [...options.systemPrompts ?? []],
1354
- messages: (options.initialMessages ?? []).map(cloneMessageSnapshot),
1355
- phase: "model-step",
1356
- step: options.initialStep ?? 1,
1357
- maxSteps: options.maxSteps,
1358
- replayDecisions: {},
1359
- startedAt: options.startedAt,
1360
- updatedAt: options.startedAt
1361
- };
1362
- }
1363
- function cloneAgentWorkflowTurnState(state) {
1364
- return {
1365
- ...state,
1366
- systemPrompts: [...state.systemPrompts],
1367
- messages: state.messages.map(cloneMessageSnapshot),
1368
- ...state.usage ? { usage: cloneUsage2(state.usage) } : {},
1369
- ...state.turnState ? { turnState: structuredClone(state.turnState) } : {},
1370
- ...state.lastModelStep ? { lastModelStep: structuredClone(state.lastModelStep) } : {},
1371
- replayDecisions: Object.fromEntries(
1372
- Object.entries(state.replayDecisions).map(([toolCallId, decision]) => [
1373
- toolCallId,
1374
- { ...decision }
1375
- ])
1376
- )
1377
- };
1378
- }
1379
1758
 
1380
- // src/runtime/workflow-planner/helpers.ts
1381
- function accumulateWorkflowUsage(current, next) {
1382
- if (!next) {
1383
- return current ? { ...current } : void 0;
1384
- }
1385
- if (!current) {
1386
- return { ...next };
1387
- }
1388
- return {
1389
- inputTokens: (current.inputTokens ?? 0) + (next.inputTokens ?? 0),
1390
- outputTokens: (current.outputTokens ?? 0) + (next.outputTokens ?? 0),
1391
- totalTokens: (current.totalTokens ?? 0) + (next.totalTokens ?? 0),
1392
- cacheReadTokens: (current.cacheReadTokens ?? 0) + (next.cacheReadTokens ?? 0),
1393
- cacheWriteTokens: (current.cacheWriteTokens ?? 0) + (next.cacheWriteTokens ?? 0)
1394
- };
1395
- }
1759
+ // src/execution/workflow-planner/helpers.ts
1396
1760
  function cloneRelevantReplayDecisions(state, snapshot) {
1397
1761
  return Object.fromEntries(
1398
1762
  snapshot.toolCalls.flatMap((toolCall) => {
@@ -1401,18 +1765,29 @@ function cloneRelevantReplayDecisions(state, snapshot) {
1401
1765
  })
1402
1766
  );
1403
1767
  }
1404
- function findNextUnresolvedToolCall(snapshot) {
1768
+ function findAllUnresolvedToolCalls(snapshot) {
1405
1769
  const resolvedIds = new Set(
1406
1770
  snapshot.toolResults.map((toolResult) => toolResult.toolCallId)
1407
1771
  );
1408
- return snapshot.toolCalls.find(
1772
+ return snapshot.toolCalls.filter(
1409
1773
  (toolCall) => !resolvedIds.has(toolCall.toolCallId)
1410
1774
  );
1411
1775
  }
1412
1776
 
1413
- // src/runtime/workflow-planner/plan.ts
1777
+ // src/execution/workflow-planner/plan.ts
1414
1778
  function planNextAgentWorkflowOperation(state) {
1415
1779
  switch (state.phase) {
1780
+ case "input-commit":
1781
+ if (!state.pendingInput) {
1782
+ throw new Error(
1783
+ `Workflow state for session '${state.sessionId}' is in input-commit phase without a pending input message`
1784
+ );
1785
+ }
1786
+ return {
1787
+ kind: "input-commit",
1788
+ sessionId: state.sessionId,
1789
+ messages: [structuredClone(state.pendingInput)]
1790
+ };
1416
1791
  case "model-step":
1417
1792
  return {
1418
1793
  kind: "model-step",
@@ -1428,24 +1803,37 @@ function planNextAgentWorkflowOperation(state) {
1428
1803
  `Workflow state for session '${state.sessionId}' is in tool-batch phase without a stepCommit snapshot`
1429
1804
  );
1430
1805
  }
1431
- const nextToolCall = findNextUnresolvedToolCall(
1806
+ const allUnresolved = findAllUnresolvedToolCalls(
1432
1807
  state.lastModelStep.stepCommit
1433
1808
  );
1434
- if (!nextToolCall) {
1809
+ if (allUnresolved.length === 0) {
1435
1810
  throw new Error(
1436
1811
  `Workflow state for session '${state.sessionId}' is in tool-batch phase without unresolved tool calls`
1437
1812
  );
1438
1813
  }
1814
+ if (allUnresolved.length === 1) {
1815
+ return {
1816
+ kind: "tool-call",
1817
+ step: state.step,
1818
+ sessionId: state.sessionId,
1819
+ toolCall: structuredClone(allUnresolved[0]),
1820
+ turnState: state.turnState ? structuredClone(state.turnState) : void 0,
1821
+ replayDecision: cloneRelevantReplayDecisions(
1822
+ state,
1823
+ state.lastModelStep.stepCommit
1824
+ )[allUnresolved[0].toolCallId]
1825
+ };
1826
+ }
1439
1827
  return {
1440
- kind: "tool-call",
1828
+ kind: "tool-batch",
1441
1829
  step: state.step,
1442
1830
  sessionId: state.sessionId,
1443
- toolCall: structuredClone(nextToolCall),
1831
+ toolCalls: allUnresolved.map((tc) => structuredClone(tc)),
1444
1832
  turnState: state.turnState ? structuredClone(state.turnState) : void 0,
1445
- replayDecision: cloneRelevantReplayDecisions(
1833
+ replayDecisions: cloneRelevantReplayDecisions(
1446
1834
  state,
1447
1835
  state.lastModelStep.stepCommit
1448
- )[nextToolCall.toolCallId]
1836
+ )
1449
1837
  };
1450
1838
  }
1451
1839
  case "step-commit":
@@ -1478,12 +1866,12 @@ function planNextAgentWorkflowOperation(state) {
1478
1866
  }
1479
1867
  }
1480
1868
 
1481
- // src/runtime/workflow-planner/apply.ts
1869
+ // src/execution/workflow-planner/apply.ts
1482
1870
  function applyAgentWorkflowModelStepResult(state, result, updatedAt) {
1483
1871
  const next = cloneAgentWorkflowTurnState(state);
1484
1872
  next.lastModelStep = structuredClone(result);
1485
1873
  next.turnState = structuredClone(result.turnState);
1486
- next.usage = accumulateWorkflowUsage(next.usage, result.usage);
1874
+ next.usage = accumulateUsage(next.usage, result.usage);
1487
1875
  next.updatedAt = updatedAt;
1488
1876
  next.phase = result.finishReason === "tool-calls" ? "tool-batch" : "output-commit";
1489
1877
  return next;
@@ -1550,6 +1938,10 @@ function applyAgentWorkflowCommitResult(state, result, updatedAt) {
1550
1938
  ];
1551
1939
  next.updatedAt = updatedAt;
1552
1940
  switch (state.phase) {
1941
+ case "input-commit":
1942
+ next.phase = "model-step";
1943
+ delete next.pendingInput;
1944
+ return next;
1553
1945
  case "step-commit":
1554
1946
  next.step = state.step + 1;
1555
1947
  next.phase = "model-step";
@@ -1581,30 +1973,38 @@ function failAgentWorkflowTurnState(state, error, updatedAt) {
1581
1973
  }
1582
1974
 
1583
1975
  export {
1584
- convertAgentMessagesToModelMessages,
1585
1976
  createAgentTurnStepCommitBatch,
1586
1977
  createAgentTurnState,
1587
1978
  advanceAgentTurnState,
1588
1979
  failAgentTurnState,
1589
1980
  AgentTurnEngine,
1590
1981
  createAgentTurnEngine,
1982
+ estimateTokens,
1983
+ estimateMessageTokens,
1984
+ estimateConversationTokens,
1985
+ DEFAULT_CONTEXT_LIMITS,
1986
+ getUsableTokenLimit,
1987
+ findCutPoint,
1988
+ ContextManager,
1591
1989
  prepareModelStep,
1592
1990
  DoomLoopError,
1593
1991
  ContextOverflowError,
1594
1992
  processStepStream,
1595
- processStream,
1596
1993
  runModelStep,
1597
1994
  runToolBatch,
1598
1995
  commitStep,
1599
1996
  commitOutput,
1600
1997
  defaultAgentTaskCheckpointStrategy,
1601
1998
  createAgentTaskRunner,
1999
+ createAgentWorkflowTurnState,
2000
+ cloneAgentWorkflowTurnState,
2001
+ applyWorkflowInterventions,
2002
+ drainWorkflowInterventions,
2003
+ queueWorkflowFollowUps,
1602
2004
  snapshotAgentWorkflowMessage,
1603
2005
  snapshotAgentWorkflowMessages,
1604
2006
  restoreAgentWorkflowMessage,
1605
2007
  restoreAgentWorkflowMessages,
1606
- createAgentWorkflowTurnState,
1607
- cloneAgentWorkflowTurnState,
1608
2008
  planNextAgentWorkflowOperation,
1609
2009
  applyAgentWorkflowModelStepResult,
1610
2010
  applyAgentWorkflowToolBatchResult,