Class: SwarmSDK::Tools::Read
- Includes:
- PathResolver
- Defined in:
- lib/swarm_sdk/tools/read.rb
Overview
Read tool for reading file contents from the filesystem
Supports reading entire files or specific line ranges with line numbers. Provides system reminders to guide proper usage. Tracks reads per agent for enforcing read-before-write/edit rules.
Constant Summary collapse
- CONVERTERS =
List of available document converters
[ DocumentConverters::PdfConverter, DocumentConverters::DocxConverter, DocumentConverters::XlsxConverter, ].freeze
Instance Attribute Summary
Attributes included from PathResolver
Class Method Summary collapse
Instance Method Summary collapse
- #execute(file_path:, offset: nil, limit: nil) ⇒ Object
-
#initialize(agent_name:, directory:) ⇒ Read
constructor
Initialize the Read tool for a specific agent.
-
#name ⇒ Object
Override name to return simple "Read" instead of full class path.
Methods inherited from Base
removable, removable?, #removable?
Constructor Details
#initialize(agent_name:, directory:) ⇒ Read
Initialize the Read tool for a specific agent
74 75 76 77 |
# File 'lib/swarm_sdk/tools/read.rb', line 74 def initialize(agent_name:, directory:) super() initialize_agent_context(agent_name: agent_name, directory: directory) end |
Class Method Details
.creation_requirements ⇒ Object
32 33 34 |
# File 'lib/swarm_sdk/tools/read.rb', line 32 def creation_requirements [:agent_name, :directory] end |
Instance Method Details
#execute(file_path:, offset: nil, limit: nil) ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/swarm_sdk/tools/read.rb', line 84 def execute(file_path:, offset: nil, limit: nil) # Validate file path return validation_error("file_path is required") if file_path.nil? || file_path.to_s.strip.empty? # CRITICAL: Resolve path against agent directory resolved_path = resolve_path(file_path) unless File.exist?(resolved_path) return validation_error("File does not exist: #{file_path}") end # Check if it's a directory if File.directory?(resolved_path) return validation_error("Path is a directory, not a file. Use Bash with ls to read directories.") end # Check if it's a document and try to convert it converter = find_converter_for_file(resolved_path) if converter result = converter.new.convert(resolved_path) # For document files, register the converted text content # Extract text from result (may be wrapped in system-reminder tags) if result.is_a?(String) # Remove system-reminder wrapper if present to get clean text for digest text_content = result.gsub(%r{<system-reminder>.*?</system-reminder>}m, "").strip Stores::ReadTracker.register_read(@agent_name, resolved_path, text_content) end return result end # Try to read as text, handle binary files separately content = read_file_content(resolved_path) # If content is a Content object (binary file), track with binary digest and return if content.is_a?(RubyLLM::Content) # For binary files, read raw bytes for digest binary_content = File.binread(resolved_path) Stores::ReadTracker.register_read(@agent_name, resolved_path, binary_content) return content end # Return early if we got an error message or system reminder return content if content.is_a?(String) && (content.start_with?("Error:") || content.start_with?("<system-reminder>")) # At this point, we have valid text content - register the read with digest Stores::ReadTracker.register_read(@agent_name, resolved_path, content) # Check if file is empty if content.empty? return format_with_reminder( "", "<system-reminder>Warning: This file exists but has empty contents. This may be intentional or indicate an issue.</system-reminder>", ) end # Split into lines and apply offset/limit lines = content.lines total_lines = lines.count # Apply offset if specified (1-indexed) start_line = offset ? offset - 1 : 0 start_line = [start_line, 0].max # Ensure non-negative if start_line >= total_lines return validation_error("Offset #{offset} exceeds file length (#{total_lines} lines)") end lines = lines.drop(start_line) # Apply limit if specified, otherwise use default default_limit = SwarmSDK.config.read_line_limit effective_limit = limit || default_limit lines = lines.take(effective_limit) truncated = limit.nil? && total_lines > default_limit # Format with line numbers (cat -n style) max_line_length = SwarmSDK.config.line_character_limit output_lines = lines.each_with_index.map do |line, idx| line_number = start_line + idx + 1 display_line = line.chomp # Truncate long lines if display_line.length > max_line_length display_line = display_line[0...max_line_length] display_line += "... (line truncated)" end # Add line indicator for better readability "#{line_number.to_s.rjust(6)}→#{display_line}" end output = output_lines.join("\n") # Add system reminder about usage reminder = build_system_reminder(file_path, truncated, total_lines) format_with_reminder(output, reminder) rescue StandardError => e error("Unexpected error reading file: #{e.class.name} - #{e.message}") end |
#name ⇒ Object
Override name to return simple "Read" instead of full class path
80 81 82 |
# File 'lib/swarm_sdk/tools/read.rb', line 80 def name "Read" end |