extractReadFilesFromMessages function

FileStateCache extractReadFilesFromMessages(
  1. List<Map<String, dynamic>> messages,
  2. String cwd, {
  3. int maxSize = _askReadFileStateCacheSize,
})

Extract read files from messages to populate the file state cache.

Implementation

FileStateCache extractReadFilesFromMessages(
  List<Map<String, dynamic>> messages,
  String cwd, {
  int maxSize = _askReadFileStateCacheSize,
}) {
  final cache = createFileStateCacheWithSizeLimit(maxSize);

  // First pass: find tool_use blocks.
  final fileReadToolUseIds = <String, String>{}; // toolUseId -> filePath
  final fileWriteToolUseIds =
      <String, ({String filePath, String content})>{}; // toolUseId -> data
  final fileEditToolUseIds = <String, String>{}; // toolUseId -> filePath

  for (final message in messages) {
    if (message['type'] != 'assistant') continue;
    final content = message['message']?['content'];
    if (content is! List) continue;

    for (final block in content) {
      if (block is! Map<String, dynamic> || block['type'] != 'tool_use') {
        continue;
      }

      final name = block['name'] as String?;
      final input = block['input'] as Map<String, dynamic>?;
      final id = block['id'] as String?;
      if (id == null || input == null) continue;

      if (name == 'FileRead') {
        final path = input['file_path'] as String?;
        if (path != null && input['offset'] == null && input['limit'] == null) {
          fileReadToolUseIds[id] = _expandPath(path, cwd);
        }
      } else if (name == 'FileWrite') {
        final path = input['file_path'] as String?;
        final writeContent = input['content'] as String?;
        if (path != null && writeContent != null) {
          fileWriteToolUseIds[id] = (
            filePath: _expandPath(path, cwd),
            content: writeContent,
          );
        }
      } else if (name == 'FileEdit') {
        final path = input['file_path'] as String?;
        if (path != null) {
          fileEditToolUseIds[id] = _expandPath(path, cwd);
        }
      }
    }
  }

  // Second pass: find corresponding tool results.
  for (final message in messages) {
    if (message['type'] != 'user') continue;
    final content = message['message']?['content'];
    if (content is! List) continue;

    for (final block in content) {
      if (block is! Map<String, dynamic> || block['type'] != 'tool_result') {
        continue;
      }

      final toolUseId = block['tool_use_id'] as String?;
      if (toolUseId == null) continue;

      // Handle Read tool results.
      final readFilePath = fileReadToolUseIds[toolUseId];
      if (readFilePath != null) {
        final resultContent = block['content'] as String?;
        if (resultContent != null &&
            !resultContent.startsWith(_fileUnchangedStub)) {
          // Remove system-reminder blocks.
          final processed = resultContent.replaceAll(
            RegExp(r'<system-reminder>[\s\S]*?<\/system-reminder>'),
            '',
          );

          final fileContent = processed
              .split('\n')
              .map(_stripLineNumberPrefix)
              .join('\n')
              .trim();

          final timestamp = message['timestamp'] as String?;
          if (timestamp != null) {
            final ts = DateTime.parse(timestamp).millisecondsSinceEpoch;
            cache.set(
              readFilePath,
              FileStateCacheEntry(content: fileContent, timestamp: ts),
            );
          }
        }
      }

      // Handle Write tool results.
      final writeData = fileWriteToolUseIds[toolUseId];
      if (writeData != null) {
        final timestamp = message['timestamp'] as String?;
        if (timestamp != null) {
          final ts = DateTime.parse(timestamp).millisecondsSinceEpoch;
          cache.set(
            writeData.filePath,
            FileStateCacheEntry(content: writeData.content, timestamp: ts),
          );
        }
      }

      // Handle Edit tool results — read from disk.
      final editFilePath = fileEditToolUseIds[toolUseId];
      if (editFilePath != null && block['is_error'] != true) {
        try {
          final file = File(editFilePath);
          final diskContent = file.readAsStringSync();
          final mtime = file.statSync().modified.millisecondsSinceEpoch;
          cache.set(
            editFilePath,
            FileStateCacheEntry(content: diskContent, timestamp: mtime),
          );
        } catch (_) {
          // File deleted or inaccessible.
        }
      }
    }
  }

  return cache;
}