query method

Future<Message> query({
  1. required List<Message> messages,
  2. OnTextDelta? onTextDelta,
  3. OnToolUse? onToolUse,
  4. OnToolResult? onToolResult,
  5. OnApiError? onError,
  6. OnPermissionRequest? onPermissionRequest,
  7. OnCompaction? onCompaction,
  8. void onStreamEvent(
    1. StreamEvent
    )?,
})

Run a full query with tool use loop (streaming).

Implementation

Future<Message> query({
  required List<Message> messages,
  OnTextDelta? onTextDelta,
  OnToolUse? onToolUse,
  OnToolResult? onToolResult,
  OnApiError? onError,
  OnPermissionRequest? onPermissionRequest,
  OnCompaction? onCompaction,
  void Function(StreamEvent)? onStreamEvent,
}) async {
  var conversationMessages = List<Message>.from(messages);
  var turn = 0;

  while (turn < config.maxTurns) {
    turn++;

    // Phase 1: Microcompact old tool results before API call
    if (config.enableMicrocompact && compactionService != null) {
      conversationMessages = compactionService!.microcompact(
        conversationMessages,
      );
    }

    // Phase 2: Auto-compact if approaching context limit
    if (config.enableCompaction && compactionService != null) {
      final compactionResult = await compactionService!.autoCompactIfNeeded(
        messages: conversationMessages,
        systemPrompt: systemPrompt,
        contextWindow: config.contextWindow,
      );
      if (compactionResult != null) {
        conversationMessages = compactionResult.compactedMessages;
        onCompaction?.call(compactionResult);
      }
    }

    // API call
    final assistantMessage = await _streamOneRound(
      messages: conversationMessages,
      onTextDelta: onTextDelta,
      onStreamEvent: onStreamEvent,
    );

    conversationMessages.add(assistantMessage);

    // Track for session memory
    sessionMemory?.trackMessage(assistantMessage);

    final toolUses = assistantMessage.toolUses;
    if (toolUses.isEmpty) {
      return assistantMessage;
    }

    // Execute all tool calls with permission checks
    final toolResults = <ContentBlock>[];
    for (final toolUse in toolUses) {
      final tool = toolRegistry.get(toolUse.name);

      // Permission check
      if (tool != null && onPermissionRequest != null) {
        final permResult = await _checkPermission(
          tool,
          toolUse.input,
          onPermissionRequest,
        );
        if (!permResult) {
          toolResults.add(
            ToolResultBlock(
              toolUseId: toolUse.id,
              content: 'Permission denied by user for ${toolUse.name}.',
              isError: true,
            ),
          );
          continue;
        }
      }

      onToolUse?.call(toolUse.name, toolUse.input);

      final result = await toolRegistry.execute(toolUse.name, toolUse.input);

      onToolResult?.call(toolUse.name, result);

      toolResults.add(
        ToolResultBlock(
          toolUseId: toolUse.id,
          content: result.content,
          isError: result.isError,
        ),
      );
    }

    final userMessage = Message(role: MessageRole.user, content: toolResults);
    conversationMessages.add(userMessage);

    // Track tool results for session memory
    sessionMemory?.trackMessage(userMessage);
  }

  return Message.assistant(
    '[Max turns reached (${config.maxTurns}). Stopping tool use loop.]',
  );
}