runTools function

Stream<MessageUpdate> runTools(
  1. List<ToolUseBlock> toolUseMessages,
  2. List<AssistantMessage> assistantMessages,
  3. CanUseToolFn canUseTool,
  4. ToolUseContext toolUseContext,
)

Run tools — handles both serial and concurrent execution.

Implementation

Stream<MessageUpdate> runTools(
  List<ToolUseBlock> toolUseMessages,
  List<AssistantMessage> assistantMessages,
  CanUseToolFn canUseTool,
  ToolUseContext toolUseContext,
) async* {
  var currentContext = toolUseContext;

  for (final batch in partitionToolCalls(toolUseMessages, currentContext)) {
    if (batch.isConcurrencySafe) {
      // Run concurrency-safe batch in parallel
      final futures = <Future<List<MessageUpdate>>>[];

      for (final toolUse in batch.blocks) {
        currentContext.setInProgressToolUseIDs?.call(
          (prev) => {...prev, toolUse.id},
        );

        final assistantMsg = assistantMessages.firstWhere(
          (a) => a.content.any(
            (c) => c is Map && c['type'] == 'tool_use' && c['id'] == toolUse.id,
          ),
          orElse: () => assistantMessages.first,
        );

        futures.add(
          runToolUse(
            toolUse,
            assistantMsg,
            canUseTool,
            currentContext,
          ).toList(),
        );
      }

      final results = await Future.wait(futures);
      for (final updates in results) {
        for (final update in updates) {
          yield MessageUpdate(
            message: update.message,
            newContext: currentContext,
          );
        }
      }

      // Mark all as complete
      for (final toolUse in batch.blocks) {
        _markToolUseAsComplete(currentContext, toolUse.id);
      }
    } else {
      // Run non-concurrency-safe batch serially
      for (final toolUse in batch.blocks) {
        currentContext.setInProgressToolUseIDs?.call(
          (prev) => {...prev, toolUse.id},
        );

        final assistantMsg = assistantMessages.firstWhere(
          (a) => a.content.any(
            (c) => c is Map && c['type'] == 'tool_use' && c['id'] == toolUse.id,
          ),
          orElse: () => assistantMessages.first,
        );

        await for (final update in runToolUse(
          toolUse,
          assistantMsg,
          canUseTool,
          currentContext,
        )) {
          if (update.contextModifier != null) {
            currentContext = update.contextModifier!.modifyContext(
              currentContext,
            );
          }
          yield MessageUpdate(
            message: update.message,
            newContext: currentContext,
          );
        }

        _markToolUseAsComplete(currentContext, toolUse.id);
      }
    }
  }
}