Class: SwarmSDK::V3::Tools::Edit

Inherits:
Base
  • Object
show all
Defined in:
lib/swarm_sdk/v3/tools/edit.rb

Overview

Edit tool for performing exact string replacements in files

Uses exact string matching to find and replace content. Requires the file to have been read first via the Read tool.

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#name

Constructor Details

#initialize(agent_name:, directory:, read_tracker:) ⇒ Edit

Returns a new instance of Edit.

Parameters:

  • agent_name (Symbol, String)

    Agent identifier

  • directory (String)

    Agent’s working directory

  • read_tracker (ReadTracker)

    Shared read tracker for enforcement



51
52
53
54
55
56
# File 'lib/swarm_sdk/v3/tools/edit.rb', line 51

def initialize(agent_name:, directory:, read_tracker:)
  super()
  @agent_name = agent_name.to_sym
  @directory = File.expand_path(directory)
  @read_tracker = read_tracker
end

Class Method Details

.creation_requirementsArray<Symbol>

Returns Constructor requirements.

Returns:

  • (Array<Symbol>)

    Constructor requirements



13
14
15
# File 'lib/swarm_sdk/v3/tools/edit.rb', line 13

def creation_requirements
  [:agent_name, :directory, :read_tracker]
end

Instance Method Details

#execute(file_path:, old_string:, new_string:, replace_all: false) ⇒ String

Execute file edit

Parameters:

  • file_path (String)

    Path to the file

  • old_string (String)

    Text to find

  • new_string (String)

    Replacement text

  • replace_all (Boolean) (defaults to: false)

    Replace all occurrences

Returns:

  • (String)

    Success or error message



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/swarm_sdk/v3/tools/edit.rb', line 65

def execute(file_path:, old_string:, new_string:, replace_all: false)
  return validation_error("file_path is required") if file_path.nil? || file_path.to_s.strip.empty?
  return validation_error("old_string is required") if old_string.nil? || old_string.empty?
  return validation_error("new_string is required") if new_string.nil?
  return validation_error("old_string and new_string must be different.") if old_string == new_string

  resolved_path = resolve_path(file_path)
  return validation_error("File does not exist: #{file_path}") unless File.exist?(resolved_path)

  unless @read_tracker.file_read?(@agent_name, resolved_path)
    return validation_error(
      "Cannot edit file without reading it first. " \
        "Use the Read tool on '#{file_path}' before editing.",
    )
  end

  content = File.read(resolved_path, encoding: "UTF-8")

  unless content.include?(old_string)
    return validation_error("old_string not found in file. Make sure it matches exactly, including all whitespace and indentation.")
  end

  occurrences = content.scan(old_string).count

  if !replace_all && occurrences > 1
    return validation_error(
      "Found #{occurrences} occurrences of old_string. " \
        "Provide more context to make the match unique, or use replace_all: true.",
    )
  end

  new_content = replace_all ? content.gsub(old_string, new_string) : content.sub(old_string, new_string)
  File.write(resolved_path, new_content, encoding: "UTF-8")

  replaced_count = replace_all ? occurrences : 1
  "Successfully replaced #{replaced_count} occurrence(s) in #{file_path}"
rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError
  error("File contains invalid UTF-8. Cannot edit binary files.")
rescue Errno::EACCES
  error("Permission denied: Cannot read or write file '#{file_path}'")
rescue StandardError => e
  error("Unexpected error editing file: #{e.class.name} - #{e.message}")
end