swarm_sdk 2.0.0.pre.2

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 (68) hide show
  1. checksums.yaml +7 -0
  2. data/lib/swarm_sdk/agent/builder.rb +333 -0
  3. data/lib/swarm_sdk/agent/chat/context_tracker.rb +271 -0
  4. data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
  5. data/lib/swarm_sdk/agent/chat/logging_helpers.rb +99 -0
  6. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +114 -0
  7. data/lib/swarm_sdk/agent/chat.rb +779 -0
  8. data/lib/swarm_sdk/agent/context.rb +108 -0
  9. data/lib/swarm_sdk/agent/definition.rb +335 -0
  10. data/lib/swarm_sdk/configuration.rb +251 -0
  11. data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
  12. data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
  13. data/lib/swarm_sdk/context_compactor.rb +340 -0
  14. data/lib/swarm_sdk/hooks/adapter.rb +359 -0
  15. data/lib/swarm_sdk/hooks/context.rb +163 -0
  16. data/lib/swarm_sdk/hooks/definition.rb +80 -0
  17. data/lib/swarm_sdk/hooks/error.rb +29 -0
  18. data/lib/swarm_sdk/hooks/executor.rb +146 -0
  19. data/lib/swarm_sdk/hooks/registry.rb +143 -0
  20. data/lib/swarm_sdk/hooks/result.rb +150 -0
  21. data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
  22. data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
  23. data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
  24. data/lib/swarm_sdk/log_collector.rb +83 -0
  25. data/lib/swarm_sdk/log_stream.rb +69 -0
  26. data/lib/swarm_sdk/markdown_parser.rb +46 -0
  27. data/lib/swarm_sdk/permissions/config.rb +239 -0
  28. data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
  29. data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
  30. data/lib/swarm_sdk/permissions/validator.rb +173 -0
  31. data/lib/swarm_sdk/permissions_builder.rb +122 -0
  32. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +237 -0
  33. data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
  34. data/lib/swarm_sdk/result.rb +97 -0
  35. data/lib/swarm_sdk/swarm/agent_initializer.rb +224 -0
  36. data/lib/swarm_sdk/swarm/all_agents_builder.rb +62 -0
  37. data/lib/swarm_sdk/swarm/builder.rb +240 -0
  38. data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
  39. data/lib/swarm_sdk/swarm/tool_configurator.rb +267 -0
  40. data/lib/swarm_sdk/swarm.rb +837 -0
  41. data/lib/swarm_sdk/tools/bash.rb +274 -0
  42. data/lib/swarm_sdk/tools/delegate.rb +152 -0
  43. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
  44. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
  45. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
  46. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
  47. data/lib/swarm_sdk/tools/edit.rb +150 -0
  48. data/lib/swarm_sdk/tools/glob.rb +158 -0
  49. data/lib/swarm_sdk/tools/grep.rb +231 -0
  50. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
  51. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
  52. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
  53. data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
  54. data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
  55. data/lib/swarm_sdk/tools/read.rb +251 -0
  56. data/lib/swarm_sdk/tools/registry.rb +73 -0
  57. data/lib/swarm_sdk/tools/scratchpad_list.rb +88 -0
  58. data/lib/swarm_sdk/tools/scratchpad_read.rb +59 -0
  59. data/lib/swarm_sdk/tools/scratchpad_write.rb +88 -0
  60. data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
  61. data/lib/swarm_sdk/tools/stores/scratchpad.rb +153 -0
  62. data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
  63. data/lib/swarm_sdk/tools/todo_write.rb +216 -0
  64. data/lib/swarm_sdk/tools/write.rb +117 -0
  65. data/lib/swarm_sdk/utils.rb +50 -0
  66. data/lib/swarm_sdk/version.rb +5 -0
  67. data/lib/swarm_sdk.rb +69 -0
  68. metadata +169 -0
@@ -0,0 +1,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ class Swarm
5
+ # Handles tool creation, registration, and permissions wrapping
6
+ #
7
+ # Responsibilities:
8
+ # - Register explicit tools for agents
9
+ # - Register default tools (Read, Grep, Glob, etc.)
10
+ # - Create tool instances (with agent context)
11
+ # - Wrap tools with permissions validators
12
+ #
13
+ # This encapsulates all tool-related logic that was previously in Swarm.
14
+ class ToolConfigurator
15
+ # Default tools available to all agents (unless include_default_tools: false)
16
+ DEFAULT_TOOLS = [
17
+ :Read,
18
+ :Grep,
19
+ :Glob,
20
+ :TodoWrite,
21
+ :ScratchpadWrite,
22
+ :ScratchpadRead,
23
+ :ScratchpadList,
24
+ ].freeze
25
+
26
+ def initialize(swarm, scratchpad)
27
+ @swarm = swarm
28
+ @scratchpad = scratchpad
29
+ end
30
+
31
+ # Register all tools for an agent (both explicit and default)
32
+ #
33
+ # @param chat [AgentChat] The chat instance to register tools with
34
+ # @param agent_name [Symbol] Name of the agent
35
+ # @param agent_definition [AgentDefinition] Agent definition object
36
+ def register_all_tools(chat:, agent_name:, agent_definition:)
37
+ register_explicit_tools(chat, agent_definition.tools, agent_name: agent_name, agent_definition: agent_definition)
38
+ register_default_tools(chat, agent_name: agent_name, agent_definition: agent_definition)
39
+ end
40
+
41
+ # Create a tool instance by name
42
+ #
43
+ # File tools and TodoWrite require agent context for tracking state.
44
+ # Scratchpad tools require shared scratchpad instance.
45
+ #
46
+ # This method is public for testing delegation from Swarm.
47
+ #
48
+ # @param tool_name [Symbol, String] Tool name
49
+ # @param agent_name [Symbol] Agent name for context
50
+ # @param directory [String] Agent's working directory
51
+ # @return [RubyLLM::Tool] Tool instance
52
+ def create_tool_instance(tool_name, agent_name, directory)
53
+ tool_name_sym = tool_name.to_sym
54
+
55
+ case tool_name_sym
56
+ when :Read
57
+ Tools::Read.new(agent_name: agent_name, directory: directory)
58
+ when :Write
59
+ Tools::Write.new(agent_name: agent_name, directory: directory)
60
+ when :Edit
61
+ Tools::Edit.new(agent_name: agent_name, directory: directory)
62
+ when :MultiEdit
63
+ Tools::MultiEdit.new(agent_name: agent_name, directory: directory)
64
+ when :Bash
65
+ Tools::Bash.new(directory: directory)
66
+ when :Glob
67
+ Tools::Glob.new(directory: directory)
68
+ when :Grep
69
+ Tools::Grep.new(directory: directory)
70
+ when :TodoWrite
71
+ Tools::TodoWrite.new(agent_name: agent_name) # TodoWrite doesn't need directory
72
+ when :ScratchpadWrite
73
+ Tools::ScratchpadWrite.create_for_scratchpad(@scratchpad)
74
+ when :ScratchpadRead
75
+ Tools::ScratchpadRead.create_for_scratchpad(@scratchpad)
76
+ when :ScratchpadList
77
+ Tools::ScratchpadList.create_for_scratchpad(@scratchpad)
78
+ else
79
+ # Regular tools - get class from registry and instantiate
80
+ tool_class = Tools::Registry.get(tool_name_sym)
81
+ raise ConfigurationError, "Unknown tool: #{tool_name}" unless tool_class
82
+
83
+ tool_class.new
84
+ end
85
+ end
86
+
87
+ # Wrap a tool instance with permissions validator if configured
88
+ #
89
+ # This method is public for testing delegation from Swarm.
90
+ #
91
+ # @param tool_instance [RubyLLM::Tool] Tool instance to wrap
92
+ # @param permissions_config [Hash, nil] Permission configuration
93
+ # @param agent_definition [AgentDefinition] Agent definition
94
+ # @return [RubyLLM::Tool] Either the wrapped tool or original tool
95
+ def wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
96
+ # Skip wrapping if no permissions or agent bypasses permissions
97
+ return tool_instance unless permissions_config
98
+ return tool_instance if agent_definition.bypass_permissions
99
+
100
+ # Create permissions config and wrap tool with validator
101
+ permissions = Permissions::Config.new(
102
+ permissions_config,
103
+ base_directory: agent_definition.directory,
104
+ )
105
+
106
+ Permissions::Validator.new(tool_instance, permissions)
107
+ end
108
+
109
+ private
110
+
111
+ # Register explicitly configured tools
112
+ #
113
+ # @param chat [AgentChat] The chat instance
114
+ # @param tool_configs [Array<Hash>] Tool configurations with optional permissions
115
+ # @param agent_name [Symbol] Agent name
116
+ # @param agent_definition [AgentDefinition] Agent definition
117
+ def register_explicit_tools(chat, tool_configs, agent_name:, agent_definition:)
118
+ tool_configs.each do |tool_config|
119
+ tool_name = tool_config[:name]
120
+ permissions_config = tool_config[:permissions]
121
+
122
+ # Create tool instance
123
+ tool_instance = create_tool_instance(tool_name, agent_name, agent_definition.directory)
124
+
125
+ # Wrap with permissions validator if configured
126
+ tool_instance = wrap_tool_with_permissions(
127
+ tool_instance,
128
+ permissions_config,
129
+ agent_definition,
130
+ )
131
+
132
+ chat.with_tool(tool_instance)
133
+ end
134
+ end
135
+
136
+ # Register default tools for agents that have include_default_tools enabled
137
+ #
138
+ # @param chat [AgentChat] The chat instance
139
+ # @param agent_name [Symbol] Agent name
140
+ # @param agent_definition [AgentDefinition] Agent definition
141
+ def register_default_tools(chat, agent_name:, agent_definition:)
142
+ return unless agent_definition.include_default_tools
143
+
144
+ # Get explicit tool names to avoid duplicates
145
+ explicit_tool_names = agent_definition.tools.map { |t| t[:name] }.to_set
146
+
147
+ DEFAULT_TOOLS.each do |tool_name|
148
+ # Skip if already registered explicitly
149
+ next if explicit_tool_names.include?(tool_name)
150
+
151
+ tool_instance = create_tool_instance(tool_name, agent_name, agent_definition.directory)
152
+
153
+ # Resolve permissions for default tool (same logic as AgentDefinition)
154
+ # Agent-level permissions override default permissions
155
+ permissions_config = agent_definition.agent_permissions[tool_name] ||
156
+ agent_definition.default_permissions[tool_name]
157
+
158
+ # Wrap with permissions validator if configured
159
+ tool_instance = wrap_tool_with_permissions(
160
+ tool_instance,
161
+ permissions_config,
162
+ agent_definition,
163
+ )
164
+
165
+ chat.with_tool(tool_instance)
166
+ end
167
+ end
168
+
169
+ # Register agent delegation tools
170
+ #
171
+ # Creates delegation tools that allow one agent to call another.
172
+ #
173
+ # @param chat [AgentChat] The chat instance
174
+ # @param delegate_names [Array<Symbol>] Names of agents to delegate to
175
+ # @param agent_name [Symbol] Name of the agent doing the delegating
176
+ def register_delegation_tools(chat, delegate_names, agent_name:)
177
+ return if delegate_names.empty?
178
+
179
+ delegate_names.each do |delegate_name|
180
+ delegate_name = delegate_name.to_sym
181
+
182
+ unless @agents.key?(delegate_name)
183
+ raise ConfigurationError, "Agent delegates to unknown agent '#{delegate_name}'"
184
+ end
185
+
186
+ # Create a tool that delegates to the specified agent
187
+ delegate_agent = @agents[delegate_name]
188
+ delegate_definition = @agent_definitions[delegate_name]
189
+
190
+ tool = Tools::Delegate.new(
191
+ delegate_name: delegate_name.to_s,
192
+ delegate_description: delegate_definition.description,
193
+ delegate_chat: delegate_agent,
194
+ agent_name: agent_name,
195
+ swarm: @swarm,
196
+ hook_registry: @hook_registry,
197
+ )
198
+
199
+ chat.with_tool(tool)
200
+ end
201
+ end
202
+
203
+ # Pass 4: Configure hook system
204
+ #
205
+ # Setup the callback system for each agent.
206
+ def pass_4_configure_hooks
207
+ @agents.each do |agent_name, chat|
208
+ agent_definition = @agent_definitions[agent_name]
209
+
210
+ chat.setup_hooks(
211
+ registry: @hook_registry,
212
+ agent_definition: agent_definition,
213
+ swarm: @swarm,
214
+ ) if chat.respond_to?(:setup_hooks)
215
+ end
216
+ end
217
+
218
+ # Pass 5: Apply YAML hooks if present
219
+ #
220
+ # If loaded from YAML, apply agent-specific hooks.
221
+ def pass_5_apply_yaml_hooks
222
+ return unless @config_for_hooks
223
+
224
+ @agents.each do |agent_name, chat|
225
+ agent_def = @config_for_hooks.agents[agent_name]
226
+ next unless agent_def&.hooks
227
+
228
+ HooksAdapter.apply_agent_hooks(chat, agent_name, agent_def.hooks, @swarm.name)
229
+ end
230
+ end
231
+
232
+ # Create an AgentChat instance
233
+ #
234
+ # NOTE: This is dead code, left over from refactoring. AgentInitializer
235
+ # now handles agent creation. This should be removed in a cleanup pass.
236
+ #
237
+ # @param agent_name [Symbol] Agent name
238
+ # @param agent_definition [AgentDefinition] Agent definition
239
+ # @param tool_configurator [ToolConfigurator] Tool configurator
240
+ # @return [AgentChat] Configured chat instance
241
+ def create_agent_chat(agent_name, agent_definition, tool_configurator)
242
+ chat = AgentChat.new(
243
+ definition: agent_definition.to_h,
244
+ global_semaphore: @global_semaphore,
245
+ )
246
+
247
+ # Set agent name on provider for logging (if provider supports it)
248
+ chat.provider.agent_name = agent_name if chat.provider.respond_to?(:agent_name=)
249
+
250
+ # Register tools
251
+ tool_configurator.register_all_tools(
252
+ chat: chat,
253
+ agent_name: agent_name,
254
+ agent_definition: agent_definition,
255
+ )
256
+
257
+ # Register MCP servers if any
258
+ if agent_definition.mcp_servers.any?
259
+ mcp_configurator = McpConfigurator.new(@swarm)
260
+ mcp_configurator.register_mcp_servers(chat, agent_definition.mcp_servers, agent_name: agent_name)
261
+ end
262
+
263
+ chat
264
+ end
265
+ end
266
+ end
267
+ end