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.
- checksums.yaml +7 -0
- data/lib/swarm_sdk/agent/builder.rb +333 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +271 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
- data/lib/swarm_sdk/agent/chat/logging_helpers.rb +99 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +114 -0
- data/lib/swarm_sdk/agent/chat.rb +779 -0
- data/lib/swarm_sdk/agent/context.rb +108 -0
- data/lib/swarm_sdk/agent/definition.rb +335 -0
- data/lib/swarm_sdk/configuration.rb +251 -0
- data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
- data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
- data/lib/swarm_sdk/context_compactor.rb +340 -0
- data/lib/swarm_sdk/hooks/adapter.rb +359 -0
- data/lib/swarm_sdk/hooks/context.rb +163 -0
- data/lib/swarm_sdk/hooks/definition.rb +80 -0
- data/lib/swarm_sdk/hooks/error.rb +29 -0
- data/lib/swarm_sdk/hooks/executor.rb +146 -0
- data/lib/swarm_sdk/hooks/registry.rb +143 -0
- data/lib/swarm_sdk/hooks/result.rb +150 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
- data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
- data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
- data/lib/swarm_sdk/log_collector.rb +83 -0
- data/lib/swarm_sdk/log_stream.rb +69 -0
- data/lib/swarm_sdk/markdown_parser.rb +46 -0
- data/lib/swarm_sdk/permissions/config.rb +239 -0
- data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
- data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
- data/lib/swarm_sdk/permissions/validator.rb +173 -0
- data/lib/swarm_sdk/permissions_builder.rb +122 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +237 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
- data/lib/swarm_sdk/result.rb +97 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +224 -0
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +62 -0
- data/lib/swarm_sdk/swarm/builder.rb +240 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +267 -0
- data/lib/swarm_sdk/swarm.rb +837 -0
- data/lib/swarm_sdk/tools/bash.rb +274 -0
- data/lib/swarm_sdk/tools/delegate.rb +152 -0
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
- data/lib/swarm_sdk/tools/edit.rb +150 -0
- data/lib/swarm_sdk/tools/glob.rb +158 -0
- data/lib/swarm_sdk/tools/grep.rb +231 -0
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
- data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
- data/lib/swarm_sdk/tools/read.rb +251 -0
- data/lib/swarm_sdk/tools/registry.rb +73 -0
- data/lib/swarm_sdk/tools/scratchpad_list.rb +88 -0
- data/lib/swarm_sdk/tools/scratchpad_read.rb +59 -0
- data/lib/swarm_sdk/tools/scratchpad_write.rb +88 -0
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
- data/lib/swarm_sdk/tools/stores/scratchpad.rb +153 -0
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
- data/lib/swarm_sdk/tools/todo_write.rb +216 -0
- data/lib/swarm_sdk/tools/write.rb +117 -0
- data/lib/swarm_sdk/utils.rb +50 -0
- data/lib/swarm_sdk/version.rb +5 -0
- data/lib/swarm_sdk.rb +69 -0
- 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
|