swarm_sdk 2.7.14 → 3.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +16 -0
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +4 -1
  4. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  5. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  6. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  7. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  8. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  9. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  10. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  11. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  12. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  13. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  14. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  15. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  16. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  17. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  18. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  19. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  20. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  24. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  25. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  26. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  27. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  28. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  29. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  30. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  31. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  32. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  33. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  34. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  35. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  36. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  37. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  38. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  39. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  40. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  41. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  42. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  43. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  44. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  45. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  46. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  47. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  48. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  49. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  50. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  51. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  52. data/lib/swarm_sdk/v3.rb +145 -0
  53. metadata +83 -148
  54. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  55. data/lib/swarm_sdk/agent/builder.rb +0 -705
  56. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  57. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  58. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  59. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  60. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  61. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  62. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  63. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  64. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  65. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  66. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  67. data/lib/swarm_sdk/agent/context.rb +0 -115
  68. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  69. data/lib/swarm_sdk/agent/definition.rb +0 -588
  70. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  71. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  72. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  73. data/lib/swarm_sdk/agent_registry.rb +0 -146
  74. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  75. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  76. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  77. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  78. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  79. data/lib/swarm_sdk/config.rb +0 -368
  80. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  81. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  82. data/lib/swarm_sdk/configuration.rb +0 -165
  83. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  84. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  85. data/lib/swarm_sdk/context_compactor.rb +0 -335
  86. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  87. data/lib/swarm_sdk/context_management/context.rb +0 -328
  88. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  89. data/lib/swarm_sdk/defaults.rb +0 -251
  90. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  91. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  92. data/lib/swarm_sdk/hooks/context.rb +0 -197
  93. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  94. data/lib/swarm_sdk/hooks/error.rb +0 -29
  95. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  96. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  97. data/lib/swarm_sdk/hooks/result.rb +0 -150
  98. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  99. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  100. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  101. data/lib/swarm_sdk/log_collector.rb +0 -227
  102. data/lib/swarm_sdk/log_stream.rb +0 -127
  103. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  104. data/lib/swarm_sdk/model_aliases.json +0 -8
  105. data/lib/swarm_sdk/models.json +0 -44002
  106. data/lib/swarm_sdk/models.rb +0 -161
  107. data/lib/swarm_sdk/node_context.rb +0 -245
  108. data/lib/swarm_sdk/observer/builder.rb +0 -81
  109. data/lib/swarm_sdk/observer/config.rb +0 -45
  110. data/lib/swarm_sdk/observer/manager.rb +0 -248
  111. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  112. data/lib/swarm_sdk/permissions/config.rb +0 -239
  113. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  114. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  115. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  116. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  117. data/lib/swarm_sdk/plugin.rb +0 -309
  118. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  119. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  120. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  121. data/lib/swarm_sdk/restore_result.rb +0 -65
  122. data/lib/swarm_sdk/result.rb +0 -241
  123. data/lib/swarm_sdk/snapshot.rb +0 -156
  124. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  125. data/lib/swarm_sdk/state_restorer.rb +0 -476
  126. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  127. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  128. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  129. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  130. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  131. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  132. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  133. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  134. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  135. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  136. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  137. data/lib/swarm_sdk/swarm.rb +0 -973
  138. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  139. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  140. data/lib/swarm_sdk/tools/base.rb +0 -63
  141. data/lib/swarm_sdk/tools/bash.rb +0 -280
  142. data/lib/swarm_sdk/tools/clock.rb +0 -46
  143. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  144. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  145. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  146. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  147. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  148. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  149. data/lib/swarm_sdk/tools/edit.rb +0 -145
  150. data/lib/swarm_sdk/tools/glob.rb +0 -166
  151. data/lib/swarm_sdk/tools/grep.rb +0 -235
  152. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  153. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  154. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  155. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  156. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  157. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  158. data/lib/swarm_sdk/tools/read.rb +0 -261
  159. data/lib/swarm_sdk/tools/registry.rb +0 -205
  160. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  161. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  163. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  164. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  165. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  166. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  167. data/lib/swarm_sdk/tools/think.rb +0 -100
  168. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  169. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  170. data/lib/swarm_sdk/tools/write.rb +0 -112
  171. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  172. data/lib/swarm_sdk/utils.rb +0 -68
  173. data/lib/swarm_sdk/validation_result.rb +0 -33
  174. data/lib/swarm_sdk/version.rb +0 -5
  175. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  176. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  177. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  178. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  179. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  180. data/lib/swarm_sdk/workflow.rb +0 -589
  181. data/lib/swarm_sdk.rb +0 -721
@@ -1,261 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- # Read tool for reading file contents from the filesystem
6
- #
7
- # Supports reading entire files or specific line ranges with line numbers.
8
- # Provides system reminders to guide proper usage.
9
- # Tracks reads per agent for enforcing read-before-write/edit rules.
10
- class Read < Base
11
- include PathResolver
12
-
13
- # NOTE: Line length and limit now accessed via SwarmSDK.config
14
-
15
- # List of available document converters
16
- CONVERTERS = [
17
- DocumentConverters::PdfConverter,
18
- DocumentConverters::DocxConverter,
19
- DocumentConverters::XlsxConverter,
20
- ].freeze
21
-
22
- # Build dynamic description based on available gems
23
- available_formats = CONVERTERS.select(&:available?).map(&:format_name)
24
- doc_support_text = if available_formats.any?
25
- "- Document files: #{available_formats.join(", ")} are converted to text"
26
- else
27
- ""
28
- end
29
-
30
- # Factory pattern: declare what parameters this tool needs for instantiation
31
- class << self
32
- def creation_requirements
33
- [:agent_name, :directory]
34
- end
35
- end
36
-
37
- description <<~DESC
38
- Reads a file from the local filesystem. You can access any file directly by using this tool.
39
- Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid.
40
- It is okay to read a file that does not exist; an error will be returned.
41
-
42
- Supports text, binary, and document files:
43
- - Text files are returned with line numbers
44
- - Binary files (images) are returned as visual content for analysis
45
- - Supported image formats: PNG, JPG, GIF, WEBP, BMP, TIFF, SVG, ICO
46
- #{doc_support_text}
47
-
48
- IMPORTANT - Path Handling:
49
- - Relative paths (e.g., "tmp/file.txt", "src/main.rb") are resolved relative to your agent's working directory
50
- - Absolute paths (e.g., "/tmp/file.txt", "/etc/passwd") are treated as system absolute paths
51
- - When the user says "tmp/file.txt" they mean the tmp directory in your working directory, NOT /tmp
52
- - Only use absolute paths (starting with /) when explicitly referring to system-level paths
53
- DESC
54
-
55
- param :file_path,
56
- type: "string",
57
- desc: "Path to the file. Use relative paths (e.g., 'tmp/file.txt') for files in your working directory, or absolute paths (e.g., '/etc/passwd') for system files.",
58
- required: true
59
-
60
- param :offset,
61
- type: "integer",
62
- desc: "The line number to start reading from (1-indexed). Only provide if the file is too large to read at once.",
63
- required: false
64
-
65
- param :limit,
66
- type: "integer",
67
- desc: "The number of lines to read. Only provide if the file is too large to read at once.",
68
- required: false
69
-
70
- # Initialize the Read tool for a specific agent
71
- #
72
- # @param agent_name [Symbol, String] The agent identifier
73
- # @param directory [String] Agent's working directory
74
- def initialize(agent_name:, directory:)
75
- super()
76
- initialize_agent_context(agent_name: agent_name, directory: directory)
77
- end
78
-
79
- # Override name to return simple "Read" instead of full class path
80
- def name
81
- "Read"
82
- end
83
-
84
- def execute(file_path:, offset: nil, limit: nil)
85
- # Validate file path
86
- return validation_error("file_path is required") if file_path.nil? || file_path.to_s.strip.empty?
87
-
88
- # CRITICAL: Resolve path against agent directory
89
- resolved_path = resolve_path(file_path)
90
-
91
- unless File.exist?(resolved_path)
92
- return validation_error("File does not exist: #{file_path}")
93
- end
94
-
95
- # Check if it's a directory
96
- if File.directory?(resolved_path)
97
- return validation_error("Path is a directory, not a file. Use Bash with ls to read directories.")
98
- end
99
-
100
- # Check if it's a document and try to convert it
101
- converter = find_converter_for_file(resolved_path)
102
- if converter
103
- result = converter.new.convert(resolved_path)
104
- # For document files, register the converted text content
105
- # Extract text from result (may be wrapped in system-reminder tags)
106
- if result.is_a?(String)
107
- # Remove system-reminder wrapper if present to get clean text for digest
108
- text_content = result.gsub(%r{<system-reminder>.*?</system-reminder>}m, "").strip
109
- Stores::ReadTracker.register_read(@agent_name, resolved_path, text_content)
110
- end
111
- return result
112
- end
113
-
114
- # Try to read as text, handle binary files separately
115
- content = read_file_content(resolved_path)
116
-
117
- # If content is a Content object (binary file), track with binary digest and return
118
- if content.is_a?(RubyLLM::Content)
119
- # For binary files, read raw bytes for digest
120
- binary_content = File.binread(resolved_path)
121
- Stores::ReadTracker.register_read(@agent_name, resolved_path, binary_content)
122
- return content
123
- end
124
-
125
- # Return early if we got an error message or system reminder
126
- return content if content.is_a?(String) && (content.start_with?("Error:") || content.start_with?("<system-reminder>"))
127
-
128
- # At this point, we have valid text content - register the read with digest
129
- Stores::ReadTracker.register_read(@agent_name, resolved_path, content)
130
-
131
- # Check if file is empty
132
- if content.empty?
133
- return format_with_reminder(
134
- "",
135
- "<system-reminder>Warning: This file exists but has empty contents. This may be intentional or indicate an issue.</system-reminder>",
136
- )
137
- end
138
-
139
- # Split into lines and apply offset/limit
140
- lines = content.lines
141
- total_lines = lines.count
142
-
143
- # Apply offset if specified (1-indexed)
144
- start_line = offset ? offset - 1 : 0
145
- start_line = [start_line, 0].max # Ensure non-negative
146
-
147
- if start_line >= total_lines
148
- return validation_error("Offset #{offset} exceeds file length (#{total_lines} lines)")
149
- end
150
-
151
- lines = lines.drop(start_line)
152
-
153
- # Apply limit if specified, otherwise use default
154
- default_limit = SwarmSDK.config.read_line_limit
155
- effective_limit = limit || default_limit
156
- lines = lines.take(effective_limit)
157
- truncated = limit.nil? && total_lines > default_limit
158
-
159
- # Format with line numbers (cat -n style)
160
- max_line_length = SwarmSDK.config.line_character_limit
161
- output_lines = lines.each_with_index.map do |line, idx|
162
- line_number = start_line + idx + 1
163
- display_line = line.chomp
164
-
165
- # Truncate long lines
166
- if display_line.length > max_line_length
167
- display_line = display_line[0...max_line_length]
168
- display_line += "... (line truncated)"
169
- end
170
-
171
- # Add line indicator for better readability
172
- "#{line_number.to_s.rjust(6)}→#{display_line}"
173
- end
174
-
175
- output = output_lines.join("\n")
176
-
177
- # Add system reminder about usage
178
- reminder = build_system_reminder(file_path, truncated, total_lines)
179
- format_with_reminder(output, reminder)
180
- rescue StandardError => e
181
- error("Unexpected error reading file: #{e.class.name} - #{e.message}")
182
- end
183
-
184
- private
185
-
186
- # Find the appropriate converter for a file based on extension
187
- def find_converter_for_file(file_path)
188
- ext = File.extname(file_path).downcase
189
- CONVERTERS.find { |converter| converter.extensions.include?(ext) }
190
- end
191
-
192
- def format_with_reminder(content, reminder)
193
- return content if reminder.nil? || reminder.empty?
194
-
195
- [content, "", reminder].join("\n")
196
- end
197
-
198
- def build_system_reminder(_file_path, truncated, total_lines)
199
- reminders = []
200
-
201
- reminders << "<system-reminder>"
202
- reminders << "Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior."
203
-
204
- if truncated
205
- reminders << ""
206
- reminders << "Note: This file has #{total_lines} lines but only the first #{SwarmSDK.config.read_line_limit} lines are shown. Use the offset and limit parameters to read additional sections if needed."
207
- end
208
-
209
- reminders << "</system-reminder>"
210
-
211
- reminders.join("\n")
212
- end
213
-
214
- def read_file_content(file_path)
215
- content = File.read(file_path, encoding: "UTF-8")
216
-
217
- # Check if the content is valid UTF-8
218
- unless content.valid_encoding?
219
- # Binary file detected
220
- if supported_binary_file?(file_path)
221
- return RubyLLM::Content.new("File: #{File.basename(file_path)}", file_path)
222
- else
223
- return "Error: File contains binary data and cannot be displayed as text. This may be an executable or other unsupported binary file."
224
- end
225
- end
226
-
227
- content
228
- rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError
229
- # Binary file detected
230
- if supported_binary_file?(file_path)
231
- RubyLLM::Content.new("File: #{File.basename(file_path)}", file_path)
232
- else
233
- "Error: File contains binary data and cannot be displayed as text. This may be an executable or other unsupported binary file."
234
- end
235
- rescue Errno::EACCES
236
- error("Permission denied: Cannot read file '#{file_path}'")
237
- rescue StandardError => e
238
- error("Failed to read file: #{e.message}")
239
- end
240
-
241
- def supported_binary_file?(file_path)
242
- ext = File.extname(file_path).downcase
243
- # Supported binary file types that can be sent to the model
244
- # Images only - documents are converted to text
245
- supported_formats = [
246
- ".png",
247
- ".jpg",
248
- ".jpeg",
249
- ".gif",
250
- ".webp",
251
- ".bmp",
252
- ".tiff",
253
- ".tif",
254
- ".svg",
255
- ".ico",
256
- ]
257
- supported_formats.include?(ext)
258
- end
259
- end
260
- end
261
- end
@@ -1,205 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- # Registry for built-in SwarmSDK tools
6
- #
7
- # Maps tool names (symbols) to their RubyLLM::Tool classes.
8
- # Provides validation, lookup, and factory functionality for tool registration.
9
- #
10
- # ## Tool Creation Pattern
11
- #
12
- # Tools register themselves with their creation requirements via the `tool_factory` method.
13
- # This eliminates the need for a giant case statement in ToolConfigurator.
14
- #
15
- # Tools fall into three categories:
16
- # 1. **No params**: Simple tools with no initialization requirements (Think, Clock)
17
- # 2. **Directory only**: Tools needing working directory (Bash, Grep, Glob)
18
- # 3. **Agent context**: Tools needing agent tracking (Read, Write, Edit, MultiEdit)
19
- # 4. **Scratchpad**: Tools needing scratchpad storage instance
20
- #
21
- # @example Adding a new tool with creation requirements
22
- # # In the tool class:
23
- # class MyTool < RubyLLM::Tool
24
- # def self.creation_requirements
25
- # [:agent_name, :directory]
26
- # end
27
- # end
28
- #
29
- # # In registry:
30
- # BUILTIN_TOOLS = {
31
- # MyTool: SwarmSDK::Tools::MyTool,
32
- # }
33
- #
34
- # Note: Plugin-provided tools (e.g., memory tools) are NOT in this registry.
35
- # They are registered via SwarmSDK::PluginRegistry instead.
36
- class Registry
37
- # All available built-in tools
38
- #
39
- # Maps tool names to their classes. The class must respond to `creation_requirements`
40
- # to specify what parameters are needed for instantiation.
41
- BUILTIN_TOOLS = {
42
- Read: SwarmSDK::Tools::Read,
43
- Write: SwarmSDK::Tools::Write,
44
- Edit: SwarmSDK::Tools::Edit,
45
- MultiEdit: SwarmSDK::Tools::MultiEdit,
46
- Bash: SwarmSDK::Tools::Bash,
47
- Grep: SwarmSDK::Tools::Grep,
48
- Glob: SwarmSDK::Tools::Glob,
49
- TodoWrite: SwarmSDK::Tools::TodoWrite,
50
- ScratchpadWrite: :scratchpad, # Requires scratchpad storage instance
51
- ScratchpadRead: :scratchpad, # Requires scratchpad storage instance
52
- ScratchpadList: :scratchpad, # Requires scratchpad storage instance
53
- Think: SwarmSDK::Tools::Think,
54
- WebFetch: SwarmSDK::Tools::WebFetch,
55
- Clock: SwarmSDK::Tools::Clock,
56
- }.freeze
57
-
58
- class << self
59
- # Get tool class by name
60
- #
61
- # Note: Plugin-provided tools are NOT returned by this method.
62
- # They are managed by SwarmSDK::PluginRegistry instead.
63
- #
64
- # @param name [Symbol, String] Tool name
65
- # @return [Class, Symbol, nil] Tool class, :scratchpad marker, or nil if not found
66
- def get(name)
67
- name_sym = name.to_sym
68
- BUILTIN_TOOLS[name_sym]
69
- end
70
-
71
- # Create a tool instance using the Factory Pattern
72
- #
73
- # Uses the tool's `creation_requirements` class method to determine
74
- # what parameters to pass to the constructor.
75
- #
76
- # @param name [Symbol, String] Tool name
77
- # @param context [Hash] Available context for tool creation
78
- # @option context [Symbol] :agent_name Agent identifier
79
- # @option context [String] :directory Agent's working directory
80
- # @option context [Object] :scratchpad_storage Scratchpad storage instance
81
- # @return [RubyLLM::Tool] Instantiated tool
82
- # @raise [ConfigurationError] If tool is unknown or has unmet requirements
83
- def create(name, context = {})
84
- name_sym = name.to_sym
85
- tool_entry = BUILTIN_TOOLS[name_sym]
86
-
87
- raise ConfigurationError, "Unknown tool: #{name}" unless tool_entry
88
-
89
- # Handle scratchpad tools specially (they use factory methods)
90
- if tool_entry == :scratchpad
91
- return create_scratchpad_tool(name_sym, context[:scratchpad_storage])
92
- end
93
-
94
- # Get the tool class and its requirements
95
- tool_class = tool_entry
96
-
97
- # Check if tool defines creation requirements
98
- if tool_class.respond_to?(:creation_requirements)
99
- requirements = tool_class.creation_requirements
100
- params = extract_params(requirements, context, name)
101
- tool_class.new(**params)
102
- else
103
- # No requirements - simple instantiation
104
- tool_class.new
105
- end
106
- end
107
-
108
- # Get multiple tool classes by names
109
- #
110
- # @param names [Array<Symbol, String>] Tool names
111
- # @return [Array<Class>] Array of tool classes
112
- # @raise [ConfigurationError] If any tool name is invalid
113
- def get_many(names)
114
- names.map do |name|
115
- tool_class = get(name)
116
- unless tool_class
117
- raise ConfigurationError,
118
- "Unknown tool: #{name}. Available tools: #{available_names.join(", ")}"
119
- end
120
-
121
- tool_class
122
- end
123
- end
124
-
125
- # Check if a tool exists
126
- #
127
- # Note: Only checks built-in tools. Plugin-provided tools are checked
128
- # via SwarmSDK::PluginRegistry.plugin_tool?() instead.
129
- #
130
- # @param name [Symbol, String] Tool name
131
- # @return [Boolean]
132
- def exists?(name)
133
- name_sym = name.to_sym
134
- BUILTIN_TOOLS.key?(name_sym)
135
- end
136
-
137
- # Get all available built-in tool names
138
- #
139
- # Note: Does NOT include plugin-provided tools. To get all available tools
140
- # including plugins, combine with SwarmSDK::PluginRegistry.tools.
141
- #
142
- # @return [Array<Symbol>]
143
- def available_names
144
- BUILTIN_TOOLS.keys
145
- end
146
-
147
- # Validate tool names
148
- #
149
- # @param names [Array<Symbol, String>] Tool names to validate
150
- # @return [Array<Symbol>] Invalid tool names
151
- def validate(names)
152
- names.reject { |name| exists?(name) }
153
- end
154
-
155
- private
156
-
157
- # Extract required parameters from context
158
- #
159
- # @param requirements [Array<Symbol>] Required parameter names
160
- # @param context [Hash] Available context
161
- # @param tool_name [Symbol] Tool name for error messages
162
- # @return [Hash] Parameters to pass to tool constructor
163
- # @raise [ConfigurationError] If required parameter is missing
164
- def extract_params(requirements, context, tool_name)
165
- params = {}
166
-
167
- requirements.each do |req|
168
- unless context.key?(req)
169
- raise ConfigurationError,
170
- "Tool #{tool_name} requires #{req} but it was not provided in context"
171
- end
172
-
173
- params[req] = context[req]
174
- end
175
-
176
- params
177
- end
178
-
179
- # Create a scratchpad tool using its factory method
180
- #
181
- # @param name [Symbol] Scratchpad tool name
182
- # @param storage [Object] Scratchpad storage instance
183
- # @return [RubyLLM::Tool] Instantiated scratchpad tool
184
- # @raise [ConfigurationError] If storage is not provided
185
- def create_scratchpad_tool(name, storage)
186
- unless storage
187
- raise ConfigurationError,
188
- "Scratchpad tool #{name} requires scratchpad_storage in context"
189
- end
190
-
191
- case name
192
- when :ScratchpadWrite
193
- Tools::Scratchpad::ScratchpadWrite.create_for_scratchpad(storage)
194
- when :ScratchpadRead
195
- Tools::Scratchpad::ScratchpadRead.create_for_scratchpad(storage)
196
- when :ScratchpadList
197
- Tools::Scratchpad::ScratchpadList.create_for_scratchpad(storage)
198
- else
199
- raise ConfigurationError, "Unknown scratchpad tool: #{name}"
200
- end
201
- end
202
- end
203
- end
204
- end
205
- end
@@ -1,117 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- module Scratchpad
6
- # Tool for listing scratchpad entries
7
- #
8
- # Shows all entries in the shared scratchpad with their metadata.
9
- # All agents in the swarm share the same scratchpad.
10
- class ScratchpadList < Base
11
- define_method(:name) { "ScratchpadList" }
12
-
13
- description <<~DESC
14
- List all entries in scratchpad with their metadata.
15
-
16
- ## When to Use ScratchpadList
17
-
18
- Use ScratchpadList to:
19
- - Discover what content is available in the scratchpad
20
- - Check what other agents have stored
21
- - Find relevant entries before reading them
22
- - Review all stored outputs and analysis
23
- - Check entry sizes and last update times
24
-
25
- ## Best Practices
26
-
27
- - Use this before ScratchpadRead if you don't know what's stored
28
- - Filter by prefix to narrow down results (e.g., 'notes/' lists all notes)
29
- - Shows path, title, size, and last updated time for each entry
30
- - Any agent can see all scratchpad entries
31
- - Helps coordinate multi-agent workflows
32
-
33
- ## Examples
34
-
35
- - List all entries: (no prefix parameter)
36
- - List notes only: prefix='notes/'
37
- - List analysis results: prefix='analysis/'
38
- DESC
39
-
40
- param :prefix,
41
- desc: "Optional prefix to filter entries (e.g., 'notes/' to list all entries under notes/)",
42
- required: false
43
-
44
- class << self
45
- # Create a ScratchpadList tool for a specific scratchpad storage instance
46
- #
47
- # @param scratchpad_storage [Stores::ScratchpadStorage] Shared scratchpad storage instance
48
- # @return [ScratchpadList] Tool instance
49
- def create_for_scratchpad(scratchpad_storage)
50
- new(scratchpad_storage)
51
- end
52
- end
53
-
54
- # Initialize with scratchpad storage instance
55
- #
56
- # @param scratchpad_storage [Stores::ScratchpadStorage] Shared scratchpad storage instance
57
- def initialize(scratchpad_storage)
58
- super() # Call RubyLLM::Tool's initialize
59
- @scratchpad_storage = scratchpad_storage
60
- end
61
-
62
- # Execute the tool
63
- #
64
- # @param prefix [String, nil] Optional prefix to filter entries
65
- # @return [String] Formatted list of entries
66
- def execute(prefix: nil)
67
- entries = scratchpad_storage.list(prefix: prefix)
68
-
69
- if entries.empty?
70
- prefix_msg = prefix ? " with prefix '#{prefix}'" : ""
71
- return "No entries found in scratchpad#{prefix_msg}"
72
- end
73
-
74
- result = []
75
- prefix_msg = prefix ? " with prefix '#{prefix}'" : ""
76
- result << "Scratchpad entries#{prefix_msg} (#{entries.size} #{entries.size == 1 ? "entry" : "entries"}):"
77
- result << ""
78
-
79
- entries.each do |entry|
80
- time_str = entry[:updated_at].strftime("%Y-%m-%d %H:%M:%S")
81
- result << " scratchpad://#{entry[:path]}"
82
- result << " Title: #{entry[:title]}"
83
- result << " Size: #{format_bytes(entry[:size])}"
84
- result << " Updated: #{time_str}"
85
- result << ""
86
- end
87
-
88
- result.join("\n").rstrip
89
- rescue ArgumentError => e
90
- validation_error(e.message)
91
- end
92
-
93
- private
94
-
95
- attr_reader :scratchpad_storage
96
-
97
- def validation_error(message)
98
- "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
99
- end
100
-
101
- # Format bytes to human-readable size
102
- #
103
- # @param bytes [Integer] Number of bytes
104
- # @return [String] Formatted size
105
- def format_bytes(bytes)
106
- if bytes >= 1_000_000
107
- "#{(bytes.to_f / 1_000_000).round(1)}MB"
108
- elsif bytes >= 1_000
109
- "#{(bytes.to_f / 1_000).round(1)}KB"
110
- else
111
- "#{bytes}B"
112
- end
113
- end
114
- end
115
- end
116
- end
117
- end
@@ -1,97 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- module Scratchpad
6
- # Tool for reading content from scratchpad storage
7
- #
8
- # Retrieves content stored by any agent using scratchpad_write.
9
- # All agents in the swarm share the same scratchpad.
10
- class ScratchpadRead < Base
11
- define_method(:name) { "ScratchpadRead" }
12
-
13
- description <<~DESC
14
- Read content from scratchpad.
15
-
16
- ## When to Use ScratchpadRead
17
-
18
- Use ScratchpadRead to:
19
- - Retrieve previously stored content and outputs
20
- - Access detailed analysis or results from earlier steps
21
- - Read messages or notes left by other agents
22
- - Access cached computed data
23
- - Retrieve content that was too long for direct responses
24
-
25
- ## Best Practices
26
-
27
- - Any agent can read any scratchpad content
28
- - Content is returned with line numbers for easy reference
29
- - Use ScratchpadList first if you don't know what's stored
30
- - Scratchpad data is temporary and lost when swarm ends
31
- - For persistent data, use MemoryRead instead
32
-
33
- ## Examples
34
-
35
- - Read status: file_path='status'
36
- - Read analysis: file_path='api_analysis'
37
- - Read agent notes: file_path='notes/backend'
38
- DESC
39
-
40
- param :file_path,
41
- desc: "Path to read from scratchpad (e.g., 'status', 'result', 'notes/agent_x')",
42
- required: true
43
-
44
- class << self
45
- # Create a ScratchpadRead tool for a specific scratchpad storage instance
46
- #
47
- # @param scratchpad_storage [Stores::ScratchpadStorage] Shared scratchpad storage instance
48
- # @return [ScratchpadRead] Tool instance
49
- def create_for_scratchpad(scratchpad_storage)
50
- new(scratchpad_storage)
51
- end
52
- end
53
-
54
- # Initialize with scratchpad storage instance
55
- #
56
- # @param scratchpad_storage [Stores::ScratchpadStorage] Shared scratchpad storage instance
57
- def initialize(scratchpad_storage)
58
- super() # Call RubyLLM::Tool's initialize
59
- @scratchpad_storage = scratchpad_storage
60
- end
61
-
62
- # Execute the tool
63
- #
64
- # @param file_path [String] Path to read from
65
- # @return [String] Content at the path with line numbers, or error message
66
- def execute(file_path:)
67
- content = scratchpad_storage.read(file_path: file_path)
68
- format_with_line_numbers(content)
69
- rescue ArgumentError => e
70
- validation_error(e.message)
71
- end
72
-
73
- private
74
-
75
- attr_reader :scratchpad_storage
76
-
77
- def validation_error(message)
78
- "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
79
- end
80
-
81
- # Format content with line numbers (same format as Read tool)
82
- #
83
- # @param content [String] Content to format
84
- # @return [String] Content with line numbers
85
- def format_with_line_numbers(content)
86
- lines = content.lines
87
- output_lines = lines.each_with_index.map do |line, idx|
88
- line_number = idx + 1
89
- display_line = line.chomp
90
- "#{line_number.to_s.rjust(6)}→#{display_line}"
91
- end
92
- output_lines.join("\n")
93
- end
94
- end
95
- end
96
- end
97
- end