@rowan-agent/agent 0.7.5 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @rowan-agent/agent
2
2
 
3
- Durable Agent Runtime. The public API consists of `AgentRuntime`, Durable
4
- Stores, Config Providers, Run handles, and Durable Run Events. An Agent is a
5
- persistent identity, not a process-local Session object.
3
+ Durable Agent Runtime. The public interface consists of `AgentRuntime`, Durable
4
+ Stores, Config Providers, Run handles, and Run Events. An Agent is a persistent
5
+ identity, not a process-local Session object.
6
6
 
7
7
  ## Quick start
8
8
 
@@ -31,7 +31,13 @@ const agentId = await runtime.createAgent({
31
31
  const run = await runtime.start(agentId, "Summarize the workspace.", {
32
32
  idempotencyKey: "run-example", // One Agent can have multiple independent Runs
33
33
  });
34
+ const observing = (async () => {
35
+ for await (const event of run.observe()) {
36
+ if (event.kind === "message_delta") process.stdout.write(event.text);
37
+ }
38
+ })();
34
39
  const boundary = await run.wait();
40
+ await observing;
35
41
  await runtime.close();
36
42
  ```
37
43
 
@@ -40,7 +46,7 @@ await runtime.close();
40
46
  1. `AgentRuntime.init({ store })` opens a Runtime Owner with an in-memory Config Provider by default. Pass `configs` when configuration must survive process boundaries.
41
47
  2. `createAgent()` creates a persistent Agent identity and binds a configuration snapshot.
42
48
  3. `start()` creates a queued Run; `run(runId)` returns a stateless Run handle.
43
- 4. `observe()` reads replayable `DurableRunEvent` values; `wait()` waits for a boundary.
49
+ 4. `observe()` follows display-oriented `RunEvent` values; `wait()` waits for an authoritative boundary.
44
50
  5. `respond()` continues an `input_required` Run; `cancel()` terminates an unfinished Run.
45
51
  6. `close()` seals the Owner and releases the Store.
46
52
 
@@ -69,16 +75,29 @@ Tools are supplied through `AgentConfig.context.tools` and persist through:
69
75
  When an external side effect cannot be confirmed, the Tool must become
70
76
  `indeterminate`; the Run then fails and is never automatically retried.
71
77
 
78
+ While running, a Tool may call `context.reportProgress(progress)` with a
79
+ JSON-safe value. Progress is live-only and may be dropped.
80
+
72
81
  ## Events
73
82
 
74
- `run.observe()` and `runtime.consume()` deliver only Durable Run Events:
83
+ `run.observe()` delivers `RunEvent` values for live presentation:
84
+
85
+ - transient `message_delta` and `tool_progress` events are live-only and
86
+ best-effort;
87
+ - durable `message_committed`, `run_transitioned`, and `tool_state_changed`
88
+ events are replayable;
89
+ - a durable `message_committed` event is the authoritative full content if a
90
+ transient delta was coalesced or dropped.
91
+
92
+ `runtime.consume()` delivers only `DurableRunEvent` values:
75
93
 
76
94
  - `message_committed`
77
95
  - `run_transitioned`
78
96
  - `tool_state_changed`
79
97
 
80
- Events and their corresponding Run aggregate changes commit in one Store
81
- transaction. Consumers persist their progress through cursors and checkpoints.
98
+ Durable events and their corresponding Run aggregate changes commit in one
99
+ Store transaction. Reliable consumers persist progress through cursors and
100
+ checkpoints; transient events never enter the Durable Store.
82
101
 
83
102
  ## Resources
84
103
 
package/dist/index.d.ts CHANGED
@@ -71,6 +71,7 @@ type PhaseExecution = {
71
71
  restore(snapshot: PhaseContextSnapshot): void;
72
72
  invokeModel(context: PhaseContext): Promise<ModelInvokeOutput>;
73
73
  executeTool(context: AgentContext, toolCall: ToolCall): Promise<ToolResult>;
74
+ executeTools(context: AgentContext, toolCalls: readonly ToolCall[]): Promise<readonly ToolResult[]>;
74
75
  };
75
76
 
76
77
  /**
@@ -461,6 +462,12 @@ type PhaseInvocation = {
461
462
  count: number;
462
463
  sourcePhaseId: string;
463
464
  };
465
+ /** Durable identity of the Run execution currently invoking a Phase. */
466
+ type PhaseExecutionIdentity = Readonly<{
467
+ agentId: string;
468
+ runId: string;
469
+ executionId: string;
470
+ }>;
464
471
  /** Everything a phase needs to execute */
465
472
  interface PhaseContext {
466
473
  systemPrompt: string;
@@ -473,6 +480,8 @@ interface PhaseContext {
473
480
  state: PhaseState;
474
481
  /** Identity and dispatch metadata for this phase execution */
475
482
  readonly invocation: PhaseInvocation;
483
+ /** Durable Run identity for cross-store integrations and idempotent commands. */
484
+ readonly execution: PhaseExecutionIdentity;
476
485
  /** Additional guideline bullets appended to the system prompt */
477
486
  promptGuidelines?: string[];
478
487
  /** Text to append after the system prompt */
@@ -597,6 +606,8 @@ type ThinkingContent = Readonly<{
597
606
  type ToolUseContent = Readonly<{
598
607
  type: "tool_use";
599
608
  toolCallId: ToolCallId;
609
+ /** Provider/model correlation, retained only to reconstruct the next request. */
610
+ providerToolCallId?: string;
600
611
  name: string;
601
612
  input: JsonValue;
602
613
  }>;
@@ -615,6 +626,8 @@ type DurableToolResult = Readonly<{
615
626
  type ToolResultContent = Readonly<{
616
627
  type: "tool_result";
617
628
  toolCallId: ToolCallId;
629
+ /** Provider/model correlation, retained only to reconstruct the next request. */
630
+ providerToolCallId?: string;
618
631
  result: ToolExecutionResult;
619
632
  }>;
620
633
  type UserContent = string | readonly (TextContent | ImageContent)[];
@@ -685,6 +698,7 @@ type RunningRunFailure = Extract<RunFailure, {
685
698
  type ToolCallState = "pending" | "running" | "completed" | "failed" | "indeterminate";
686
699
  type ToolCallSnapshotBase = Readonly<{
687
700
  id: ToolCallId;
701
+ providerToolCallId: string;
688
702
  agentId: AgentId;
689
703
  runId: RunId;
690
704
  executionId: ExecutionId;
@@ -831,6 +845,24 @@ type ToolStateChanged = DurableEventBase & (Readonly<{
831
845
  }>;
832
846
  }>);
833
847
  type DurableRunEvent = MessageCommitted | RunTransitioned | ToolStateChanged;
848
+ type MessageDelta = Readonly<{
849
+ kind: "message_delta";
850
+ durability: "transient";
851
+ runId: RunId;
852
+ executionId: ExecutionId;
853
+ messageId: MessageId;
854
+ offset: number;
855
+ text: string;
856
+ }>;
857
+ type ToolProgress = Readonly<{
858
+ kind: "tool_progress";
859
+ durability: "transient";
860
+ runId: RunId;
861
+ executionId: ExecutionId;
862
+ toolCallId: ToolCallId;
863
+ progress: JsonValue;
864
+ }>;
865
+ type RunEvent = DurableRunEvent | MessageDelta | ToolProgress;
834
866
 
835
867
  type UserInput = string | Readonly<{
836
868
  content: UserContent;
@@ -840,6 +872,7 @@ type ToolInvocationContext = Readonly<{
840
872
  agentId: AgentId;
841
873
  runId: RunId;
842
874
  toolCallId: ToolCallId;
875
+ reportProgress(progress: JsonValue): void;
843
876
  }>;
844
877
  type Tool = Readonly<{
845
878
  name: string;
@@ -930,10 +963,20 @@ type InputRequiredCommit = Readonly<{
930
963
  prompt: AssistantMessage;
931
964
  request: InputRequest;
932
965
  }>;
966
+ type ToolCallReservation = Readonly<{
967
+ providerToolCallId: string;
968
+ name: string;
969
+ args: JsonValue;
970
+ toolCallId?: ToolCallId;
971
+ }>;
933
972
  type ToolCommit = Readonly<{
934
973
  run: RunRecord;
935
974
  toolCall: ToolCallSnapshot;
936
975
  }>;
976
+ type ToolBatchCommit = Readonly<{
977
+ run: RunRecord;
978
+ toolCalls: readonly ToolCallSnapshot[];
979
+ }>;
937
980
  type RunRecord = Readonly<{
938
981
  id: RunId;
939
982
  agentId: AgentId;
@@ -1115,7 +1158,15 @@ interface OwnedStore {
1115
1158
  name: string;
1116
1159
  args: JsonValue;
1117
1160
  toolCallId?: ToolCallId;
1161
+ providerToolCallId?: string;
1118
1162
  }): Promise<ToolCommit>;
1163
+ reserveToolCalls(input: {
1164
+ runId: RunId;
1165
+ execution: ExecutionToken;
1166
+ expectedRevision: number;
1167
+ requestMessageId: MessageId;
1168
+ calls: readonly ToolCallReservation[];
1169
+ }): Promise<ToolBatchCommit>;
1119
1170
  startToolCall(input: {
1120
1171
  runId: RunId;
1121
1172
  execution: ExecutionToken;
@@ -1175,7 +1226,7 @@ interface AgentRun {
1175
1226
  observe(options?: {
1176
1227
  after?: EventCursor;
1177
1228
  signal?: AbortSignal;
1178
- }): AsyncIterable<DurableRunEvent>;
1229
+ }): AsyncIterable<RunEvent>;
1179
1230
  wait(options?: {
1180
1231
  signal?: AbortSignal;
1181
1232
  }): Promise<RunBoundary>;
@@ -1230,6 +1281,7 @@ declare class AgentRuntime implements AgentRuntime$1 {
1230
1281
  private readonly activeAgents;
1231
1282
  private readonly executions;
1232
1283
  private readonly consumers;
1284
+ private readonly transientEvents;
1233
1285
  private heartbeat?;
1234
1286
  private pumping;
1235
1287
  private closed;
@@ -1275,12 +1327,14 @@ declare class AgentRuntime implements AgentRuntime$1 {
1275
1327
  private execute;
1276
1328
  private failQueued;
1277
1329
  private executeTool;
1330
+ private executeToolBatch;
1331
+ private executeReservedTool;
1278
1332
  private requireAgent;
1279
1333
  private runConsumer;
1280
1334
  observe(runId: RunId, options?: {
1281
1335
  after?: EventCursor;
1282
1336
  signal?: AbortSignal;
1283
- }): AsyncIterable<DurableRunEvent>;
1337
+ }): AsyncIterable<RunEvent>;
1284
1338
  wait(runId: RunId, options?: {
1285
1339
  signal?: AbortSignal;
1286
1340
  }): Promise<RunBoundary>;
@@ -1390,7 +1444,20 @@ declare class InMemoryStore implements DurableStore {
1390
1444
  name: string;
1391
1445
  args: JsonValue;
1392
1446
  toolCallId?: ToolCallId;
1447
+ providerToolCallId?: string;
1393
1448
  }): ToolCommit;
1449
+ reserveToolCalls(lease: OwnerLease, input: {
1450
+ runId: RunId;
1451
+ execution: ExecutionToken;
1452
+ expectedRevision: number;
1453
+ requestMessageId: MessageId;
1454
+ calls: readonly Readonly<{
1455
+ providerToolCallId: string;
1456
+ name: string;
1457
+ args: JsonValue;
1458
+ toolCallId?: ToolCallId;
1459
+ }>[];
1460
+ }): ToolBatchCommit;
1394
1461
  startToolCall(lease: OwnerLease, input: {
1395
1462
  runId: RunId;
1396
1463
  execution: ExecutionToken;
@@ -1531,7 +1598,20 @@ declare class SqliteStore implements DurableStore {
1531
1598
  name: string;
1532
1599
  args: JsonValue;
1533
1600
  toolCallId?: ToolCallId;
1601
+ providerToolCallId?: string;
1534
1602
  }): Promise<ToolCommit>;
1603
+ reserveToolCalls(lease: OwnerLease, input: {
1604
+ runId: RunId;
1605
+ execution: ExecutionToken;
1606
+ expectedRevision: number;
1607
+ requestMessageId: MessageId;
1608
+ calls: readonly Readonly<{
1609
+ providerToolCallId: string;
1610
+ name: string;
1611
+ args: JsonValue;
1612
+ toolCallId?: ToolCallId;
1613
+ }>[];
1614
+ }): Promise<ToolBatchCommit>;
1535
1615
  startToolCall(lease: OwnerLease, input: {
1536
1616
  runId: RunId;
1537
1617
  execution: ExecutionToken;
@@ -1720,4 +1800,4 @@ declare function loadExtensionsFromPath(path: string): Promise<{
1720
1800
 
1721
1801
  declare function createCoreTools(input?: CoreToolContext): Tool[];
1722
1802
 
1723
- export { type AfterToolCall, type AgentConfig, type AgentDefinitionContext, type AgentId, type AgentListCursor, type AgentRecord, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type CoreToolContext, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionFactory, type HookEvent, type HookEventType, type HookHandler, InMemoryConfigProvider, InMemoryStore, type InputRequest, type InputRequestId, type InputRequiredCommit, type JsonObject, type JsonPrimitive, type JsonValue, type LoadExtensionsResult, type LoadedExtension, type Message, type MessageBase, type MessageCommitted, type MessageContent, type MessageId, type Metadata, type OpaqueId, type Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseInvocation, type PhaseOutput, type PhaseRegistry, type PhaseState, type RunBoundary, type RunClaim, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunSummary, type RunTransitioned, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, type Skill, SqliteStore, type TextContent, type ThinkingContent, type Tool, type ToolCallId, type ToolCallSnapshot, type ToolCallState, type ToolDefinition, type ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCoreTools, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhases, loadSkills };
1803
+ export { type AfterToolCall, type AgentConfig, type AgentDefinitionContext, type AgentId, type AgentListCursor, type AgentRecord, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type CoreToolContext, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionFactory, type HookEvent, type HookEventType, type HookHandler, InMemoryConfigProvider, InMemoryStore, type InputRequest, type InputRequestId, type InputRequiredCommit, type JsonObject, type JsonPrimitive, type JsonValue, type LoadExtensionsResult, type LoadedExtension, type Message, type MessageBase, type MessageCommitted, type MessageContent, type MessageDelta, type MessageId, type Metadata, type OpaqueId, type Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseExecutionIdentity, type PhaseInvocation, type PhaseOutput, type PhaseRegistry, type PhaseState, type RunBoundary, type RunClaim, type RunEvent, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunSummary, type RunTransitioned, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, type Skill, SqliteStore, type TextContent, type ThinkingContent, type Tool, type ToolCallId, type ToolCallSnapshot, type ToolCallState, type ToolDefinition, type ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolProgress, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCoreTools, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhases, loadSkills };
package/dist/index.js CHANGED
@@ -832,12 +832,11 @@ async function collectStreamResult(input) {
832
832
  if (event.type === "text_delta") {
833
833
  lastPartial = event.partial;
834
834
  if (!activeMessageId) {
835
- activeMessageId = input.message.start("assistant", event.text, {
835
+ activeMessageId = input.message.reserve("assistant", {
836
836
  phase: input.metadataPhase
837
837
  });
838
- } else {
839
- await input.message.update(activeMessageId, event.text);
840
838
  }
839
+ await input.message.update(activeMessageId, event.text);
841
840
  }
842
841
  if (event.type === "tool_call_start" || event.type === "tool_call_delta" || event.type === "tool_call_end") {
843
842
  activeMessageId ??= input.message.reserve("assistant", {
@@ -1541,7 +1540,7 @@ function createRunResult(config, state, outcome) {
1541
1540
  }
1542
1541
  async function completeRun(config, state, outcome) {
1543
1542
  if (outcome.display) {
1544
- const messages = createMessageManager(config.context, config.onMessage);
1543
+ const messages = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
1545
1544
  const messageId = messages.start("assistant", outcome.message, { outcomeId: outcome.id });
1546
1545
  await messages.end(messageId);
1547
1546
  }
@@ -1560,7 +1559,7 @@ function buildToolsWithRouting(config, availablePhases) {
1560
1559
  }
1561
1560
  return tools;
1562
1561
  }
1563
- function createMessageManager(context, onMessage) {
1562
+ function createMessageManager(context, onMessage, onMessageDelta) {
1564
1563
  const activeMessages = /* @__PURE__ */ new Map();
1565
1564
  const emitStart = (active) => {
1566
1565
  if (active.emitted) return;
@@ -1584,7 +1583,12 @@ function createMessageManager(context, onMessage) {
1584
1583
  const active = activeMessages.get(messageId);
1585
1584
  if (!active) return;
1586
1585
  emitStart(active);
1586
+ const offset = typeof active.message.content === "string" ? active.message.content.length : active.message.content.reduce(
1587
+ (length, part) => length + (part.type === "text" ? part.text.length : 0),
1588
+ 0
1589
+ );
1587
1590
  active.message.content = typeof active.message.content === "string" ? active.message.content + delta : [...active.message.content, { type: "text", text: delta }];
1591
+ onMessageDelta?.({ messageId, offset, text: delta });
1588
1592
  },
1589
1593
  replaceContent(messageId, content) {
1590
1594
  const active = activeMessages.get(messageId);
@@ -1670,8 +1674,8 @@ async function executePhaseWithModel(ctx) {
1670
1674
  if (executableToolCalls.length === 0) {
1671
1675
  return output;
1672
1676
  }
1673
- for (const toolCall of executableToolCalls) {
1674
- const result = await ctx.execution.executeTool(roundContext, toolCall);
1677
+ const results = await ctx.execution.executeTools(roundContext, executableToolCalls);
1678
+ for (const result of results) {
1675
1679
  const messageId = ctx.messageManager.start("tool", createToolResultContent(result), {
1676
1680
  phase: ctx.phase.name
1677
1681
  });
@@ -1731,7 +1735,7 @@ async function runPhaseLoop(config, state, registry) {
1731
1735
  }
1732
1736
  state.currentPhase = currentPhaseId;
1733
1737
  const allTools = buildToolsWithRouting(config, availablePhases);
1734
- const messageManager = createMessageManager(config.context, config.onMessage);
1738
+ const messageManager = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
1735
1739
  const toolExecutionManager = createToolExecutionManager();
1736
1740
  const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
1737
1741
  const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
@@ -1745,6 +1749,7 @@ async function runPhaseLoop(config, state, registry) {
1745
1749
  mode: "serial",
1746
1750
  instanceId: currentPhaseId
1747
1751
  },
1752
+ execution: config.execution,
1748
1753
  state: {
1749
1754
  current: currentPhaseId,
1750
1755
  available: Array.from(registry.phases.keys()),
@@ -2098,6 +2103,25 @@ async function executeToolCall(input) {
2098
2103
  toolName: input.toolCall.name
2099
2104
  };
2100
2105
  }
2106
+ async function executeToolCalls(input) {
2107
+ if (input.config.runtime?.toolsBatch) {
2108
+ const results2 = await input.config.runtime.toolsBatch({
2109
+ config: input.config,
2110
+ toolCalls: input.toolCalls
2111
+ });
2112
+ if (results2.length !== input.toolCalls.length) throw new Error("Runtime returned an invalid Tool batch result");
2113
+ return results2.map((result, index) => ({
2114
+ ...result,
2115
+ toolCallId: input.toolCalls[index].id,
2116
+ toolName: input.toolCalls[index].name
2117
+ }));
2118
+ }
2119
+ const results = [];
2120
+ for (const toolCall of input.toolCalls) {
2121
+ results.push(await executeToolCall({ ...input, toolCall }));
2122
+ }
2123
+ return results;
2124
+ }
2101
2125
  function createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry) {
2102
2126
  return {
2103
2127
  snapshot() {
@@ -2175,6 +2199,30 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
2175
2199
  await toolExecutionManager.end(result.toolCallId, result.toolName, result, !result.ok);
2176
2200
  return result;
2177
2201
  });
2202
+ },
2203
+ async executeTools(phaseContext, toolCalls) {
2204
+ return runTurn(async () => {
2205
+ const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
2206
+ for (const toolCall of toolCalls) {
2207
+ await toolExecutionManager.start(toolCall.id, toolCall.name, toolCall.args);
2208
+ }
2209
+ const results = await executeToolCalls({
2210
+ config: {
2211
+ ...config,
2212
+ context: {
2213
+ ...config.context,
2214
+ tools,
2215
+ skills: phaseContext.skills
2216
+ }
2217
+ },
2218
+ tools,
2219
+ toolCalls
2220
+ });
2221
+ for (const result of results) {
2222
+ await toolExecutionManager.end(result.toolCallId, result.toolName, result, !result.ok);
2223
+ }
2224
+ return results;
2225
+ });
2178
2226
  }
2179
2227
  };
2180
2228
  }
@@ -2197,6 +2245,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2197
2245
  count,
2198
2246
  sourcePhaseId
2199
2247
  },
2248
+ execution: config.execution,
2200
2249
  state: {
2201
2250
  current: phase.name,
2202
2251
  available: Array.from(registry.phases.keys()),
@@ -2204,7 +2253,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2204
2253
  payload
2205
2254
  }
2206
2255
  };
2207
- const messageManager = createMessageManager({ messages }, config.onMessage);
2256
+ const messageManager = createMessageManager({ messages }, config.onMessage, config.onMessageDelta);
2208
2257
  const phaseMsgId = injectPhaseContent(
2209
2258
  phase,
2210
2259
  { results: payload !== void 0 ? [{ name: sourcePhaseId, output: payload }] : [] },
@@ -2395,6 +2444,7 @@ async function executeOnce(input) {
2395
2444
  };
2396
2445
  const config = {
2397
2446
  context,
2447
+ execution: input.execution,
2398
2448
  model: input.model,
2399
2449
  stream: input.stream,
2400
2450
  maxAttempts: input.maxAttempts,
@@ -2405,6 +2455,7 @@ async function executeOnce(input) {
2405
2455
  afterPhase: input.afterPhase,
2406
2456
  beforePrompt: input.beforePrompt,
2407
2457
  onMessage: input.onMessage,
2458
+ onMessageDelta: input.onMessageDelta,
2408
2459
  onOutcome: input.onOutcome,
2409
2460
  onModelTranscript: input.onModelTranscript,
2410
2461
  runtime: input.runtime,
@@ -2911,13 +2962,13 @@ function projectAssistantContent(content) {
2911
2962
  return content.map((part) => {
2912
2963
  if (part.type === "text") return { ...part };
2913
2964
  if (part.type === "thinking") return { ...part };
2914
- return { type: "tool_use", id: part.toolCallId, name: part.name, input: part.input };
2965
+ return { type: "tool_use", id: part.providerToolCallId ?? part.toolCallId, name: part.name, input: part.input };
2915
2966
  });
2916
2967
  }
2917
2968
  function projectToolContent(content) {
2918
2969
  return content.map((part) => ({
2919
2970
  type: "tool_result",
2920
- toolUseId: part.toolCallId,
2971
+ toolUseId: part.providerToolCallId ?? part.toolCallId,
2921
2972
  content: jsonText(part.result.content),
2922
2973
  ...part.result.ok ? {} : { isError: true }
2923
2974
  }));
@@ -2931,7 +2982,8 @@ function projectTool(tool, agentId, runId) {
2931
2982
  const result = await tool.execute(args, {
2932
2983
  agentId,
2933
2984
  runId,
2934
- toolCallId: context.toolCallId
2985
+ toolCallId: context.toolCallId,
2986
+ reportProgress: () => void 0
2935
2987
  }, signal ?? new AbortController().signal);
2936
2988
  return { toolCallId: context.toolCallId, toolName: tool.name, ...result };
2937
2989
  }
@@ -3623,6 +3675,144 @@ function fromLoopResult(result) {
3623
3675
  return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
3624
3676
  }
3625
3677
 
3678
+ // src/runtime/transient-run-events.ts
3679
+ var MAX_BUFFERED_EVENTS = 128;
3680
+ var MAX_BUFFERED_TEXT = 64 * 1024;
3681
+ function deferred() {
3682
+ let resolve6;
3683
+ const promise = new Promise((done) => {
3684
+ resolve6 = done;
3685
+ });
3686
+ return { promise, resolve: resolve6 };
3687
+ }
3688
+ var TransientRunEventSubscription = class {
3689
+ queue = [];
3690
+ wake = deferred();
3691
+ version = 0;
3692
+ stopped = false;
3693
+ push(event) {
3694
+ if (this.stopped) return;
3695
+ const previous = this.queue.at(-1);
3696
+ if (previous?.kind === "message_delta" && event.kind === "message_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.offset + previous.text.length === event.offset) {
3697
+ this.queue[this.queue.length - 1] = {
3698
+ ...previous,
3699
+ text: previous.text + event.text
3700
+ };
3701
+ } else {
3702
+ if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
3703
+ this.queue.push(event);
3704
+ }
3705
+ this.trimText();
3706
+ this.notify();
3707
+ }
3708
+ shift() {
3709
+ return this.queue.shift();
3710
+ }
3711
+ clear(executionId) {
3712
+ if (executionId === void 0) this.queue.length = 0;
3713
+ else {
3714
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3715
+ if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
3716
+ }
3717
+ }
3718
+ this.notify();
3719
+ }
3720
+ clearMessage(messageId) {
3721
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3722
+ const event = this.queue[index];
3723
+ if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
3724
+ }
3725
+ this.notify();
3726
+ }
3727
+ clearTool(toolCallId) {
3728
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3729
+ const event = this.queue[index];
3730
+ if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
3731
+ }
3732
+ this.notify();
3733
+ }
3734
+ checkpoint() {
3735
+ return this.version;
3736
+ }
3737
+ changed(since) {
3738
+ if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
3739
+ return this.wake.promise;
3740
+ }
3741
+ stop() {
3742
+ if (this.stopped) return;
3743
+ this.stopped = true;
3744
+ this.queue.length = 0;
3745
+ this.notify();
3746
+ }
3747
+ notify() {
3748
+ const wake = this.wake;
3749
+ this.wake = deferred();
3750
+ this.version += 1;
3751
+ wake.resolve();
3752
+ }
3753
+ trimText() {
3754
+ let buffered = this.queue.reduce(
3755
+ (length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
3756
+ 0
3757
+ );
3758
+ while (buffered > MAX_BUFFERED_TEXT) {
3759
+ const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
3760
+ if (index < 0) return;
3761
+ const event = this.queue[index];
3762
+ const overflow = buffered - MAX_BUFFERED_TEXT;
3763
+ if (event.text.length <= overflow) {
3764
+ this.queue.splice(index, 1);
3765
+ buffered -= event.text.length;
3766
+ } else {
3767
+ this.queue[index] = {
3768
+ ...event,
3769
+ offset: event.offset + overflow,
3770
+ text: event.text.slice(overflow)
3771
+ };
3772
+ buffered -= overflow;
3773
+ }
3774
+ }
3775
+ }
3776
+ };
3777
+ var TransientRunEventHub = class {
3778
+ subscriptions = /* @__PURE__ */ new Map();
3779
+ subscribe(runId) {
3780
+ const subscription = new TransientRunEventSubscription();
3781
+ const current = this.subscriptions.get(runId) ?? /* @__PURE__ */ new Set();
3782
+ current.add(subscription);
3783
+ this.subscriptions.set(runId, current);
3784
+ return subscription;
3785
+ }
3786
+ unsubscribe(runId, subscription) {
3787
+ subscription.stop();
3788
+ const current = this.subscriptions.get(runId);
3789
+ current?.delete(subscription);
3790
+ if (current?.size === 0) this.subscriptions.delete(runId);
3791
+ }
3792
+ publish(event) {
3793
+ for (const subscription of this.subscriptions.get(event.runId) ?? []) {
3794
+ subscription.push(event);
3795
+ }
3796
+ }
3797
+ clear(runId, executionId) {
3798
+ for (const subscription of this.subscriptions.get(runId) ?? []) {
3799
+ subscription.clear(executionId);
3800
+ }
3801
+ }
3802
+ clearMessage(runId, messageId) {
3803
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearMessage(messageId);
3804
+ }
3805
+ clearTool(runId, toolCallId) {
3806
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearTool(toolCallId);
3807
+ }
3808
+ close() {
3809
+ for (const subscriptions of this.subscriptions.values()) {
3810
+ for (const subscription of subscriptions) subscription.stop();
3811
+ }
3812
+ this.subscriptions.clear();
3813
+ }
3814
+ };
3815
+
3626
3816
  // src/runtime/durable-runtime.ts
3627
3817
  var DEFAULT_CONCURRENCY = 10;
3628
3818
  var DEFAULT_POLL_MS = 25;
@@ -3636,6 +3826,7 @@ var AgentRuntime = class _AgentRuntime {
3636
3826
  activeAgents = /* @__PURE__ */ new Set();
3637
3827
  executions = /* @__PURE__ */ new Map();
3638
3828
  consumers = /* @__PURE__ */ new Map();
3829
+ transientEvents = new TransientRunEventHub();
3639
3830
  heartbeat;
3640
3831
  pumping = false;
3641
3832
  closed = false;
@@ -3693,7 +3884,7 @@ var AgentRuntime = class _AgentRuntime {
3693
3884
  if (input.signal.aborted) throw abortError();
3694
3885
  if (this.consumers.has(input.consumerId)) throw new RuntimeError("consumer_already_active", { consumerId: input.consumerId });
3695
3886
  const controller = new AbortController();
3696
- const subscription = { consumerId: input.consumerId, controller, input, caughtUp: deferred(), done: deferred() };
3887
+ const subscription = { consumerId: input.consumerId, controller, input, caughtUp: deferred2(), done: deferred2() };
3697
3888
  this.consumers.set(input.consumerId, subscription);
3698
3889
  const onAbort = () => controller.abort();
3699
3890
  input.signal.addEventListener("abort", onAbort, { once: true });
@@ -3710,8 +3901,9 @@ var AgentRuntime = class _AgentRuntime {
3710
3901
  if (this.closed) return;
3711
3902
  this.closed = true;
3712
3903
  if (this.heartbeat) clearInterval(this.heartbeat);
3713
- for (const controller of this.executions.values()) controller.abort();
3904
+ for (const execution of this.executions.values()) execution.controller.abort();
3714
3905
  this.executions.clear();
3906
+ this.transientEvents.close();
3715
3907
  for (const subscription of this.consumers.values()) subscription.controller.abort();
3716
3908
  await this.owned.sealAndReleaseOwner();
3717
3909
  }
@@ -3732,7 +3924,9 @@ var AgentRuntime = class _AgentRuntime {
3732
3924
  this.assertOpen();
3733
3925
  const snapshot = await this.owned.snapshotRun(runId);
3734
3926
  await this.owned.cancelRun({ runId, expectedRevision: snapshot.revision, ...reason === void 0 ? {} : { reason } });
3735
- this.executions.get(runId)?.abort();
3927
+ const active = this.executions.get(runId);
3928
+ if (active) this.transientEvents.clear(runId, active.executionId);
3929
+ active?.controller.abort();
3736
3930
  return boundaryFromSnapshot(await this.owned.snapshotRun(runId));
3737
3931
  }
3738
3932
  async pump() {
@@ -3745,6 +3939,8 @@ var AgentRuntime = class _AgentRuntime {
3745
3939
  if (!next) return;
3746
3940
  this.activeAgents.add(next.agentId);
3747
3941
  const task = this.execute(next).finally(() => {
3942
+ const execution = this.executions.get(next.id);
3943
+ if (execution) this.transientEvents.clear(next.id, execution.executionId);
3748
3944
  this.activeAgents.delete(next.agentId);
3749
3945
  this.executions.delete(next.id);
3750
3946
  void this.pump();
@@ -3783,7 +3979,7 @@ var AgentRuntime = class _AgentRuntime {
3783
3979
  const executionId = createId("exec");
3784
3980
  claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
3785
3981
  const controller = new AbortController();
3786
- this.executions.set(run.id, controller);
3982
+ this.executions.set(run.id, { controller, executionId });
3787
3983
  const config = resolution.config;
3788
3984
  const assembly = await assembleExtensions(config);
3789
3985
  const executionConfig = {
@@ -3811,6 +4007,11 @@ var AgentRuntime = class _AgentRuntime {
3811
4007
  const result = await executeOnce({
3812
4008
  canonicalMessages: executionContext.messages,
3813
4009
  context: executionContext,
4010
+ execution: {
4011
+ agentId: run.agentId,
4012
+ runId: run.id,
4013
+ executionId: claim.execution.executionId
4014
+ },
3814
4015
  model,
3815
4016
  stream,
3816
4017
  maxAttempts: executionConfig.maxAttempts,
@@ -3819,20 +4020,49 @@ var AgentRuntime = class _AgentRuntime {
3819
4020
  beforePhase: assembly.beforePhase,
3820
4021
  afterPhase: assembly.afterPhase,
3821
4022
  beforePrompt: assembly.beforePrompt,
4023
+ onMessageDelta: (event) => {
4024
+ const active = this.executions.get(run.id);
4025
+ if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
4026
+ this.transientEvents.publish({
4027
+ kind: "message_delta",
4028
+ durability: "transient",
4029
+ runId: run.id,
4030
+ executionId: claim.execution.executionId,
4031
+ messageId: event.messageId,
4032
+ offset: event.offset,
4033
+ text: event.text
4034
+ });
4035
+ },
3822
4036
  onContext: assembly.setContext,
3823
4037
  runtime: {
3824
4038
  tools: ({ toolCall }) => {
3825
4039
  const task = toolQueue.then(async () => {
3826
- const execution = await this.executeTool({
4040
+ const execution = await this.executeToolBatch({
4041
+ run,
4042
+ execution: claim.execution,
4043
+ expectedRevision: executionRevision,
4044
+ config: executionConfig,
4045
+ toolCalls: [toolCall],
4046
+ signal: controller.signal
4047
+ });
4048
+ executionRevision = execution.revision;
4049
+ return execution.results[0];
4050
+ });
4051
+ toolQueue = task.then(() => void 0, () => void 0);
4052
+ return task;
4053
+ },
4054
+ toolsBatch: ({ toolCalls }) => {
4055
+ const task = toolQueue.then(async () => {
4056
+ const execution = await this.executeToolBatch({
3827
4057
  run,
3828
4058
  execution: claim.execution,
3829
4059
  expectedRevision: executionRevision,
3830
4060
  config: executionConfig,
3831
- toolCall,
4061
+ toolCalls,
3832
4062
  signal: controller.signal
3833
4063
  });
3834
4064
  executionRevision = execution.revision;
3835
- return execution.result;
4065
+ return execution.results;
3836
4066
  });
3837
4067
  toolQueue = task.then(() => void 0, () => void 0);
3838
4068
  return task;
@@ -3842,15 +4072,18 @@ var AgentRuntime = class _AgentRuntime {
3842
4072
  if (result.type === "input_required") {
3843
4073
  const prompt = promptMessage(run, result.request.prompt, result.messages.length);
3844
4074
  await this.owned.commitInputRequired({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, requestId: createId("input"), prompt, checkpoint: result.checkpoint });
4075
+ this.transientEvents.clear(run.id, claim.execution.executionId);
3845
4076
  return;
3846
4077
  }
3847
4078
  if (result.type === "completed") {
3848
4079
  const output = latestAssistant(run, result.messages, claim.history.length);
3849
4080
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, outcome: durableOutcome(result.outcome), ...output ? { output } : {} });
4081
+ this.transientEvents.clear(run.id, claim.execution.executionId);
3850
4082
  return;
3851
4083
  }
3852
4084
  const failure = { code: "execution_failed", message: result.error instanceof Error ? result.error.message : "Execution failed." };
3853
4085
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, failure });
4086
+ this.transientEvents.clear(run.id, claim.execution.executionId);
3854
4087
  } catch (error) {
3855
4088
  if (!claim && error instanceof RuntimeError && ["run_state_conflict", "runtime_ownership_lost", "run_not_found"].includes(error.code)) return;
3856
4089
  if (claim) {
@@ -3871,29 +4104,75 @@ var AgentRuntime = class _AgentRuntime {
3871
4104
  });
3872
4105
  }
3873
4106
  async executeTool(input) {
3874
- const tool = input.config.context.tools.find((candidate) => candidate.name === input.toolCall.name);
3875
- const providerToolCallId = input.toolCall.id;
3876
- const toolCallId = createId("tool");
3877
- const requestMessageId = createId("msg");
3878
- const reserved = await this.owned.reserveToolCall({
4107
+ const batch = await this.executeToolBatch({ ...input, toolCalls: [input.toolCall] });
4108
+ return { result: batch.results[0], revision: batch.revision };
4109
+ }
4110
+ async executeToolBatch(input) {
4111
+ const reserved = await this.owned.reserveToolCalls({
3879
4112
  runId: input.run.id,
3880
4113
  execution: input.execution,
3881
4114
  expectedRevision: input.expectedRevision,
3882
- requestMessageId,
3883
- toolCallId,
3884
- name: input.toolCall.name,
3885
- args: toJsonValue(input.toolCall.args)
4115
+ requestMessageId: createId("msg"),
4116
+ calls: input.toolCalls.map((toolCall) => ({
4117
+ providerToolCallId: toolCall.id,
4118
+ name: toolCall.name,
4119
+ args: toJsonValue(toolCall.args)
4120
+ }))
3886
4121
  });
3887
4122
  let revision = reserved.run.revision;
4123
+ const results = [];
4124
+ for (let index = 0; index < input.toolCalls.length; index += 1) {
4125
+ const toolCall = input.toolCalls[index];
4126
+ const tool = reserved.toolCalls[index];
4127
+ const execution = await this.executeReservedTool({
4128
+ ...input,
4129
+ toolCall,
4130
+ tool,
4131
+ expectedRevision: revision
4132
+ });
4133
+ revision = execution.revision;
4134
+ results.push(execution.result);
4135
+ }
4136
+ return { results, revision };
4137
+ }
4138
+ async executeReservedTool(input) {
4139
+ const durableTool = input.config.context.tools.find((candidate) => candidate.name === input.toolCall.name);
4140
+ const providerToolCallId = input.toolCall.id;
4141
+ const toolCallId = input.tool.id;
4142
+ let revision = input.expectedRevision;
3888
4143
  const protocolFailure = (error) => ({ toolCallId: providerToolCallId, toolName: input.toolCall.name, ok: false, content: null, error });
3889
- if (!tool) {
4144
+ if (!durableTool) {
3890
4145
  const failed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: "failed", result: { ok: false, content: null, error: `Tool ${input.toolCall.name} is not available.` } });
3891
4146
  return { result: protocolFailure(`Tool ${input.toolCall.name} is not available.`), revision: failed.run.revision };
3892
4147
  }
3893
- const context = { agentId: input.run.agentId, runId: input.run.id, toolCallId };
4148
+ let progressActive = false;
4149
+ const context = {
4150
+ agentId: input.run.agentId,
4151
+ runId: input.run.id,
4152
+ toolCallId,
4153
+ reportProgress: (progress) => {
4154
+ const active = this.executions.get(input.run.id);
4155
+ if (!progressActive || this.closed || input.signal.aborted || active?.executionId !== input.execution.executionId || !isJsonValue(progress)) return;
4156
+ let copy;
4157
+ try {
4158
+ if (JSON.stringify(progress).length > 64 * 1024) return;
4159
+ copy = structuredClone(progress);
4160
+ } catch {
4161
+ return;
4162
+ }
4163
+ this.transientEvents.publish({
4164
+ kind: "tool_progress",
4165
+ durability: "transient",
4166
+ runId: input.run.id,
4167
+ executionId: input.execution.executionId,
4168
+ toolCallId,
4169
+ progress: copy
4170
+ });
4171
+ }
4172
+ };
3894
4173
  try {
3895
4174
  if (input.config.beforeToolCall) {
3896
- const decision = await input.config.beforeToolCall({ tool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
4175
+ const decision = await input.config.beforeToolCall({ tool: durableTool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
3897
4176
  if (!decision.allow) {
3898
4177
  const failed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: "failed", result: { ok: false, content: null, error: decision.reason } });
3899
4178
  return { result: protocolFailure(decision.reason), revision: failed.run.revision };
@@ -3906,10 +4185,11 @@ var AgentRuntime = class _AgentRuntime {
3906
4185
  }
3907
4186
  const started = await this.owned.startToolCall({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId });
3908
4187
  revision = started.run.revision;
4188
+ progressActive = true;
3909
4189
  try {
3910
- let result = await tool.execute(toJsonValue(input.toolCall.args), context, input.signal);
4190
+ let result = await durableTool.execute(toJsonValue(input.toolCall.args), context, input.signal);
3911
4191
  assertToolExecutionResult(result);
3912
- if (input.config.afterToolCall) result = await input.config.afterToolCall({ tool, result, context, signal: input.signal });
4192
+ if (input.config.afterToolCall) result = await input.config.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
3913
4193
  assertToolExecutionResult(result);
3914
4194
  const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
3915
4195
  return {
@@ -3920,6 +4200,9 @@ var AgentRuntime = class _AgentRuntime {
3920
4200
  const reason = error instanceof Error ? error.message : "Tool execution outcome is indeterminate.";
3921
4201
  const indeterminate = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: "indeterminate", reason, result: { ok: false, content: null, error: reason } });
3922
4202
  return { result: protocolFailure(reason), revision: indeterminate.run.revision };
4203
+ } finally {
4204
+ progressActive = false;
4205
+ this.transientEvents.clearTool(input.run.id, toolCallId);
3923
4206
  }
3924
4207
  }
3925
4208
  async requireAgent(agentId) {
@@ -3963,23 +4246,37 @@ var AgentRuntime = class _AgentRuntime {
3963
4246
  }
3964
4247
  async *observe(runId, options = {}) {
3965
4248
  let cursor = options.after;
3966
- while (true) {
3967
- if (options.signal?.aborted) throw abortError();
3968
- const snapshot = await this.owned.snapshotRun(runId);
3969
- if (["completed", "failed", "cancelled"].includes(snapshot.state)) {
3970
- const pending = await this.owned.listEvents(cursor ? { after: cursor } : {});
3971
- if (!pending.some((event) => event.runId === runId)) return;
3972
- }
3973
- const events = await this.owned.listEvents(cursor ? { after: cursor } : {});
3974
- let terminal = false;
3975
- for (const event of events) {
3976
- cursor = event.cursor;
3977
- if (event.runId !== runId) continue;
3978
- yield event;
3979
- if (event.kind === "run_transitioned" && ["completed", "failed", "cancelled"].includes(event.to)) terminal = true;
4249
+ const subscription = this.transientEvents.subscribe(runId);
4250
+ try {
4251
+ while (true) {
4252
+ if (options.signal?.aborted) throw abortError();
4253
+ const observationVersion = subscription.checkpoint();
4254
+ const snapshot = await this.owned.snapshotRun(runId);
4255
+ if (["completed", "failed", "cancelled"].includes(snapshot.state)) {
4256
+ const pending = await this.owned.listEvents(cursor ? { after: cursor } : {});
4257
+ if (!pending.some((event) => event.runId === runId)) return;
4258
+ }
4259
+ const events = await this.owned.listEvents(cursor ? { after: cursor } : {});
4260
+ for (const event of events) {
4261
+ cursor = event.cursor;
4262
+ if (event.runId !== runId) continue;
4263
+ if (event.kind === "message_committed") {
4264
+ subscription.clearMessage(event.message.id);
4265
+ } else if (event.kind === "tool_state_changed" && ["completed", "failed", "indeterminate"].includes(event.transition.to)) {
4266
+ subscription.clearTool(event.toolCall.id);
4267
+ }
4268
+ yield event;
4269
+ if (event.kind === "run_transitioned" && ["completed", "failed", "cancelled"].includes(event.to)) return;
4270
+ }
4271
+ const transient = subscription.shift();
4272
+ if (transient) {
4273
+ yield transient;
4274
+ continue;
4275
+ }
4276
+ await Promise.race([subscription.changed(observationVersion), delay(DEFAULT_POLL_MS)]);
3980
4277
  }
3981
- if (terminal) return;
3982
- await delay(DEFAULT_POLL_MS);
4278
+ } finally {
4279
+ this.transientEvents.unsubscribe(runId, subscription);
3983
4280
  }
3984
4281
  }
3985
4282
  async wait(runId, options = {}) {
@@ -3996,7 +4293,7 @@ var AgentRuntime = class _AgentRuntime {
3996
4293
  startHeartbeat() {
3997
4294
  this.heartbeat = setInterval(() => {
3998
4295
  void this.owned.renewOwner(OWNER_LEASE_MS).catch(() => {
3999
- for (const controller of this.executions.values()) controller.abort();
4296
+ for (const execution of this.executions.values()) execution.controller.abort();
4000
4297
  });
4001
4298
  }, OWNER_RENEWAL_MS);
4002
4299
  this.heartbeat.unref?.();
@@ -4070,7 +4367,7 @@ function toJsonValue(value) {
4070
4367
  assertJsonValue(value, "tool arguments");
4071
4368
  return value;
4072
4369
  }
4073
- function deferred() {
4370
+ function deferred2() {
4074
4371
  let resolve6;
4075
4372
  let reject;
4076
4373
  const promise = new Promise((res, rej) => {
@@ -4404,54 +4701,91 @@ var InMemoryStore = class _InMemoryStore {
4404
4701
  return result;
4405
4702
  }
4406
4703
  reserveToolCall(lease, input) {
4704
+ const providerToolCallId = input.providerToolCallId ?? (input.toolCallId ? String(input.toolCallId) : createId("provider"));
4705
+ const result = this.reserveToolCalls(lease, {
4706
+ runId: input.runId,
4707
+ execution: input.execution,
4708
+ expectedRevision: input.expectedRevision,
4709
+ requestMessageId: input.requestMessageId,
4710
+ calls: [{
4711
+ providerToolCallId,
4712
+ name: input.name,
4713
+ args: input.args,
4714
+ ...input.toolCallId === void 0 ? {} : { toolCallId: input.toolCallId }
4715
+ }]
4716
+ });
4717
+ return { run: result.run, toolCall: result.toolCalls[0] };
4718
+ }
4719
+ reserveToolCalls(lease, input) {
4407
4720
  this.assertOwner(lease);
4408
- assertToolValue(input.args, "tool.args");
4409
- const toolCallId = input.toolCallId ?? createId("tool");
4410
- const operationKey = `tool_reserve:${toolCallId}`;
4721
+ if (input.calls.length === 0) throw new TypeError("calls must be non-empty");
4722
+ const providerIds = /* @__PURE__ */ new Set();
4723
+ const durableIds = /* @__PURE__ */ new Set();
4724
+ for (const call of input.calls) {
4725
+ if (call.providerToolCallId.trim().length === 0) throw new TypeError("providerToolCallId must be non-empty");
4726
+ if (providerIds.has(call.providerToolCallId)) throw new TypeError("Tool provider IDs must be unique within one response");
4727
+ providerIds.add(call.providerToolCallId);
4728
+ assertToolValue(call.args, "tool.args");
4729
+ if (call.toolCallId !== void 0) {
4730
+ if (durableIds.has(call.toolCallId)) throw new TypeError("Tool Call IDs must be unique within one response");
4731
+ durableIds.add(call.toolCallId);
4732
+ }
4733
+ }
4734
+ const operationKey = `tool_reserve_batch:${input.requestMessageId}`;
4411
4735
  const operationPayload = canonicalJson([
4412
4736
  input.runId,
4413
4737
  input.expectedRevision,
4414
4738
  input.execution.executionId,
4415
4739
  input.requestMessageId,
4416
- input.name,
4417
- input.args
4740
+ input.calls
4418
4741
  ]);
4419
4742
  const replay = this.replayOperation(operationKey, operationPayload);
4420
4743
  if (replay) return clone(replay);
4421
4744
  const run = this.requireRun(input.runId);
4422
4745
  this.assertExecution(run, input.execution, input.expectedRevision);
4423
- const existing = this.toolCalls.get(toolCallId);
4424
- if (existing) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
4746
+ if (this.messages.has(input.requestMessageId)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
4425
4747
  const timestamp = createTimestamp();
4426
- const toolCall = {
4427
- id: toolCallId,
4748
+ const toolCalls = input.calls.map((call) => ({
4749
+ id: call.toolCallId ?? createId("tool"),
4750
+ providerToolCallId: call.providerToolCallId,
4428
4751
  agentId: run.agentId,
4429
4752
  runId: run.id,
4430
4753
  executionId: input.execution.executionId,
4431
4754
  requestMessageId: input.requestMessageId,
4432
- name: input.name,
4433
- args: clone(input.args),
4755
+ name: call.name,
4756
+ args: clone(call.args),
4434
4757
  state: "pending",
4435
4758
  createdAt: timestamp,
4436
4759
  updatedAt: timestamp
4437
- };
4760
+ }));
4761
+ for (const toolCall of toolCalls) {
4762
+ if (this.toolCalls.has(toolCall.id)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
4763
+ }
4438
4764
  const requestMessage = {
4439
4765
  id: input.requestMessageId,
4440
4766
  agentId: run.agentId,
4441
4767
  runId: run.id,
4442
4768
  role: "assistant",
4443
- content: [{ type: "tool_use", toolCallId, name: input.name, input: clone(input.args) }],
4769
+ content: toolCalls.map((toolCall) => ({
4770
+ type: "tool_use",
4771
+ toolCallId: toolCall.id,
4772
+ providerToolCallId: toolCall.providerToolCallId,
4773
+ name: toolCall.name,
4774
+ input: clone(toolCall.args)
4775
+ })),
4444
4776
  sequenceWithinRun: this.nextMessageSequence(run.id),
4445
4777
  createdAt: timestamp
4446
4778
  };
4447
- if (this.messages.has(requestMessage.id)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
4448
- this.toolCalls.set(toolCall.id, toolCall);
4779
+ for (const toolCall of toolCalls) this.toolCalls.set(toolCall.id, toolCall);
4449
4780
  this.messages.set(requestMessage.id, requestMessage);
4450
4781
  run.revision += 1;
4451
4782
  run.updatedAt = timestamp;
4452
4783
  this.appendMessage(run, requestMessage);
4453
- this.appendToolTransition(run, { from: null, to: "pending" }, toolCall);
4454
- const result = { run: clone(run), toolCall: clone(toolCall) };
4784
+ for (const toolCall of toolCalls) this.appendToolTransition(run, { from: null, to: "pending" }, toolCall);
4785
+ const result = {
4786
+ run: clone(run),
4787
+ toolCalls: toolCalls.map((toolCall) => clone(toolCall))
4788
+ };
4455
4789
  this.writeOperationReceipt(operationKey, operationPayload, result);
4456
4790
  return result;
4457
4791
  }
@@ -4500,7 +4834,7 @@ var InMemoryStore = class _InMemoryStore {
4500
4834
  agentId: run.agentId,
4501
4835
  runId: run.id,
4502
4836
  role: "tool",
4503
- content: [{ type: "tool_result", toolCallId: toolCall.id, result: clone(input.result) }],
4837
+ content: [{ type: "tool_result", toolCallId: toolCall.id, providerToolCallId: toolCall.providerToolCallId, result: clone(input.result) }],
4504
4838
  sequenceWithinRun: this.nextMessageSequence(run.id),
4505
4839
  createdAt: createTimestamp()
4506
4840
  };
@@ -4660,6 +4994,9 @@ var InMemoryStore = class _InMemoryStore {
4660
4994
  listEvents(lease, input = {}) {
4661
4995
  this.assertOwner(lease);
4662
4996
  const after = input.after ? this.parseCursor(input.after) : 0;
4997
+ if (after > this.eventSequence) {
4998
+ throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "beyond_waterline" });
4999
+ }
4663
5000
  return clone(this.events.filter((event) => this.parseCursor(event.cursor) > after));
4664
5001
  }
4665
5002
  openConsumer(lease, consumerId) {
@@ -4730,7 +5067,7 @@ var InMemoryStore = class _InMemoryStore {
4730
5067
  agentId: run.agentId,
4731
5068
  runId: run.id,
4732
5069
  role: "tool",
4733
- content: [{ type: "tool_result", toolCallId: toolCall.id, result }],
5070
+ content: [{ type: "tool_result", toolCallId: toolCall.id, providerToolCallId: toolCall.providerToolCallId, result }],
4734
5071
  sequenceWithinRun: this.nextMessageSequence(run.id),
4735
5072
  createdAt: createTimestamp()
4736
5073
  };
@@ -4865,6 +5202,9 @@ var MemoryOwnedStore = class {
4865
5202
  async reserveToolCall(input) {
4866
5203
  return this.store.reserveToolCall(this.lease, input);
4867
5204
  }
5205
+ async reserveToolCalls(input) {
5206
+ return this.store.reserveToolCalls(this.lease, input);
5207
+ }
4868
5208
  async startToolCall(input) {
4869
5209
  return this.store.startToolCall(this.lease, input);
4870
5210
  }
@@ -5118,6 +5458,9 @@ var SqliteStore = class {
5118
5458
  async reserveToolCall(lease, input) {
5119
5459
  return this.invoke(lease, (store, current) => store.reserveToolCall(current, input));
5120
5460
  }
5461
+ async reserveToolCalls(lease, input) {
5462
+ return this.invoke(lease, (store, current) => store.reserveToolCalls(current, input));
5463
+ }
5121
5464
  async startToolCall(lease, input) {
5122
5465
  return this.invoke(lease, (store, current) => store.startToolCall(current, input));
5123
5466
  }
@@ -5367,6 +5710,9 @@ var SqliteOwnedStore = class {
5367
5710
  reserveToolCall(input) {
5368
5711
  return this.store.reserveToolCall(this.lease, input);
5369
5712
  }
5713
+ reserveToolCalls(input) {
5714
+ return this.store.reserveToolCalls(this.lease, input);
5715
+ }
5370
5716
  startToolCall(input) {
5371
5717
  return this.store.startToolCall(this.lease, input);
5372
5718
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",