Module: Crimson::Tools::ReadFile

Defined in:
lib/crimson/tools/read_file.rb

Overview

Read file contents with optional offset/limit for large files. Detects image and binary files and returns metadata instead of content.

Constant Summary collapse

TOOL_NAME =
"read_file"
IMAGE_EXTENSIONS =

Extensions treated as viewable images.

%w[.png .jpg .jpeg .gif .webp .bmp .ico .svg .tiff .tif].freeze
BINARY_EXTENSIONS =

Extensions treated as binary (not readable as text).

%w[.zip .tar .gz .bz2 .xz .7z .rar .exe .dll .so .dylib .o .a .class .jar .wasm .pdf .doc .docx .xls .xlsx .ppt .pptx .woff .woff2 .ttf .eot .otf .mp3 .mp4 .avi .mov .mkv .flac .ogg .wav].freeze
PARAMS =

Tool parameter definitions.

{
  path: { type: "string", description: "The path to the file to read" },
  offset: { type: "integer", description: "Line number to start reading from (1-indexed). Defaults to 1." },
  limit: { type: "integer", description: "Maximum number of lines to read. Defaults to all lines." }
}.freeze

Class Method Summary collapse

Class Method Details

.anthropic_definitionHash

Returns Anthropic-compatible tool definition.

Returns:

  • (Hash)

    Anthropic-compatible tool definition



35
36
37
# File 'lib/crimson/tools/read_file.rb', line 35

def self.anthropic_definition
  Schema.build_anthropic(name: TOOL_NAME, description: "Read the contents of a file. Supports offset/limit for reading portions of large files.", parameters: PARAMS, required: ["path"])
end

.call(path:, offset: nil, limit: nil) ⇒ String

Execute the tool.

Parameters:

  • path (String)

    file path

  • offset (Integer, nil) (defaults to: nil)

    starting line (1-indexed)

  • limit (Integer, nil) (defaults to: nil)

    max lines to read

Returns:

  • (String)

    file content or error message



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/crimson/tools/read_file.rb', line 44

def self.call(path:, offset: nil, limit: nil)
  return "Error: No path provided" if path.nil? || path.strip.empty?

  expanded = File.expand_path(path)
  return "Error: File not found: #{path}" unless File.exist?(expanded)
  return "Error: Not a file: #{path}" unless File.file?(expanded)

  ext = File.extname(expanded).downcase
  return describe_image(expanded, ext) if IMAGE_EXTENSIONS.include?(ext)
  return describe_binary(expanded, ext) if BINARY_EXTENSIONS.include?(ext)

  content = File.read(expanded)
  lines = content.lines

  if offset || limit
    start_line = [(offset || 1) - 1, 0].max
    end_line = limit ? start_line + limit : lines.length
    end_line = [end_line, lines.length].min
    total = lines.length

    selected = lines[start_line...end_line]
    numbered = selected.each_with_index.map do |line, i|
      "#{start_line + i + 1}: #{line}"
    end

    "(lines #{start_line + 1}-#{end_line} of #{total})\n#{numbered.join}"
  else
    content
  end
rescue => e
  "Error reading file: #{e.message}"
end

.definitionHash

Returns OpenAI-compatible tool definition.

Returns:

  • (Hash)

    OpenAI-compatible tool definition



30
31
32
# File 'lib/crimson/tools/read_file.rb', line 30

def self.definition
  Schema.build(name: TOOL_NAME, description: "Read the contents of a file. Supports offset/limit for reading portions of large files.", parameters: PARAMS, required: ["path"])
end

.describe_binary(path, ext) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



85
86
87
88
89
# File 'lib/crimson/tools/read_file.rb', line 85

def self.describe_binary(path, ext)
  size = File.size(path)
  size_str = size > 1_048_576 ? "#{(size / 1_048_576.0).round(1)}MB" : "#{(size / 1024.0).round(1)}KB"
  "Binary file: #{File.basename(path)} (#{ext}, #{size_str}). Cannot display binary content."
end

.describe_image(path, ext) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



78
79
80
81
82
# File 'lib/crimson/tools/read_file.rb', line 78

def self.describe_image(path, ext)
  size = File.size(path)
  size_str = size > 1_048_576 ? "#{(size / 1_048_576.0).round(1)}MB" : "#{(size / 1024.0).round(1)}KB"
  "Image file: #{File.basename(path)} (#{ext}, #{size_str}). Image reading not yet supported."
end

.prepare_arguments(args) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



23
24
25
26
27
# File 'lib/crimson/tools/read_file.rb', line 23

def self.prepare_arguments(args)
  args["offset"] = args["offset"].to_i if args["offset"]
  args["limit"] = args["limit"].to_i if args["limit"]
  args
end