swarm_sdk 2.2.0 → 2.4.0
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 +4 -4
- data/lib/swarm_sdk/agent/builder.rb +58 -0
- data/lib/swarm_sdk/agent/chat.rb +527 -1059
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +262 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +11 -13
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
- data/lib/swarm_sdk/agent/context.rb +1 -2
- data/lib/swarm_sdk/agent/definition.rb +66 -154
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
- data/lib/swarm_sdk/builders/base_builder.rb +409 -0
- data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
- data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
- data/lib/swarm_sdk/concerns/validatable.rb +55 -0
- data/lib/swarm_sdk/config.rb +301 -0
- data/lib/swarm_sdk/configuration/parser.rb +353 -0
- data/lib/swarm_sdk/configuration/translator.rb +255 -0
- data/lib/swarm_sdk/configuration.rb +65 -543
- data/lib/swarm_sdk/context_compactor/token_counter.rb +2 -6
- data/lib/swarm_sdk/context_compactor.rb +6 -11
- data/lib/swarm_sdk/context_management/builder.rb +128 -0
- data/lib/swarm_sdk/context_management/context.rb +328 -0
- data/lib/swarm_sdk/defaults.rb +196 -0
- data/lib/swarm_sdk/events_to_messages.rb +18 -0
- data/lib/swarm_sdk/hooks/adapter.rb +3 -3
- data/lib/swarm_sdk/hooks/shell_executor.rb +4 -2
- data/lib/swarm_sdk/log_collector.rb +179 -29
- data/lib/swarm_sdk/log_stream.rb +29 -0
- data/lib/swarm_sdk/models.json +4333 -1
- data/lib/swarm_sdk/node_context.rb +1 -1
- data/lib/swarm_sdk/observer/builder.rb +81 -0
- data/lib/swarm_sdk/observer/config.rb +45 -0
- data/lib/swarm_sdk/observer/manager.rb +236 -0
- data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
- data/lib/swarm_sdk/plugin.rb +93 -3
- data/lib/swarm_sdk/snapshot.rb +6 -6
- data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
- data/lib/swarm_sdk/state_restorer.rb +136 -151
- data/lib/swarm_sdk/state_snapshot.rb +65 -100
- data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
- data/lib/swarm_sdk/swarm/builder.rb +44 -578
- data/lib/swarm_sdk/swarm/executor.rb +213 -0
- data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
- data/lib/swarm_sdk/swarm/tool_configurator.rb +44 -140
- data/lib/swarm_sdk/swarm.rb +146 -689
- data/lib/swarm_sdk/tools/bash.rb +14 -8
- data/lib/swarm_sdk/tools/delegate.rb +61 -43
- data/lib/swarm_sdk/tools/edit.rb +8 -13
- data/lib/swarm_sdk/tools/glob.rb +12 -4
- data/lib/swarm_sdk/tools/grep.rb +7 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
- data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
- data/lib/swarm_sdk/tools/read.rb +16 -18
- data/lib/swarm_sdk/tools/registry.rb +122 -10
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +9 -5
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
- data/lib/swarm_sdk/tools/todo_write.rb +7 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +20 -17
- data/lib/swarm_sdk/tools/write.rb +8 -13
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
- data/lib/swarm_sdk/workflow/builder.rb +143 -0
- data/lib/swarm_sdk/workflow/executor.rb +497 -0
- data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +7 -5
- data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +5 -3
- data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
- data/lib/swarm_sdk.rb +64 -104
- metadata +68 -15
- data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
class Configuration
|
|
5
|
+
# Translates parsed configuration to Swarm/Workflow using DSL builders
|
|
6
|
+
#
|
|
7
|
+
# This class is responsible for:
|
|
8
|
+
# - Creating the appropriate builder (Swarm::Builder or Workflow::Builder)
|
|
9
|
+
# - Translating parsed configuration into DSL method calls
|
|
10
|
+
# - Building the final Swarm or Workflow instance
|
|
11
|
+
#
|
|
12
|
+
# Receives a parsed Configuration::Parser and converts it to runtime objects.
|
|
13
|
+
class Translator
|
|
14
|
+
def initialize(parser)
|
|
15
|
+
@parser = parser
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_swarm(allow_filesystem_tools: nil)
|
|
19
|
+
builder = create_builder(allow_filesystem_tools)
|
|
20
|
+
|
|
21
|
+
translate_common_config(builder)
|
|
22
|
+
translate_type_specific_config(builder)
|
|
23
|
+
translate_agents(builder)
|
|
24
|
+
translate_hooks(builder)
|
|
25
|
+
|
|
26
|
+
builder.build_swarm
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def create_builder(allow_filesystem_tools)
|
|
32
|
+
if @parser.config_type == :swarm
|
|
33
|
+
Swarm::Builder.new(allow_filesystem_tools: allow_filesystem_tools)
|
|
34
|
+
else
|
|
35
|
+
Workflow::Builder.new(allow_filesystem_tools: allow_filesystem_tools)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def translate_common_config(builder)
|
|
40
|
+
builder.id(@parser.swarm_id) if @parser.swarm_id
|
|
41
|
+
builder.name(@parser.swarm_name)
|
|
42
|
+
builder.scratchpad(@parser.scratchpad_mode)
|
|
43
|
+
|
|
44
|
+
if @parser.external_swarms&.any?
|
|
45
|
+
external_swarms = @parser.external_swarms
|
|
46
|
+
builder.swarms do
|
|
47
|
+
external_swarms.each do |name, config|
|
|
48
|
+
source = config[:source]
|
|
49
|
+
case source[:type]
|
|
50
|
+
when :file
|
|
51
|
+
register(name, file: source[:value], keep_context: config[:keep_context])
|
|
52
|
+
when :yaml
|
|
53
|
+
register(name, yaml: source[:value], keep_context: config[:keep_context])
|
|
54
|
+
else
|
|
55
|
+
raise ConfigurationError, "Unknown source type: #{source[:type]}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
translate_all_agents(builder) if @parser.all_agents_config.any?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def translate_type_specific_config(builder)
|
|
65
|
+
if @parser.config_type == :swarm
|
|
66
|
+
builder.lead(@parser.lead_agent)
|
|
67
|
+
else
|
|
68
|
+
builder.start_node(@parser.start_node)
|
|
69
|
+
translate_nodes(builder)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def translate_hooks(builder)
|
|
74
|
+
return if @parser.swarm_hooks.none?
|
|
75
|
+
|
|
76
|
+
@parser.swarm_hooks.each do |event, hook_specs|
|
|
77
|
+
Array(hook_specs).each do |spec|
|
|
78
|
+
if spec[:type] == "command"
|
|
79
|
+
builder.hook(event, command: spec[:command], timeout: spec[:timeout])
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def translate_all_agents(builder)
|
|
86
|
+
all_agents_cfg = @parser.all_agents_config
|
|
87
|
+
all_agents_hks = @parser.all_agents_hooks
|
|
88
|
+
|
|
89
|
+
builder.all_agents do
|
|
90
|
+
tools(*all_agents_cfg[:tools]) if all_agents_cfg[:tools]&.any?
|
|
91
|
+
model(all_agents_cfg[:model]) if all_agents_cfg[:model]
|
|
92
|
+
provider(all_agents_cfg[:provider]) if all_agents_cfg[:provider]
|
|
93
|
+
base_url(all_agents_cfg[:base_url]) if all_agents_cfg[:base_url]
|
|
94
|
+
api_version(all_agents_cfg[:api_version]) if all_agents_cfg[:api_version]
|
|
95
|
+
timeout(all_agents_cfg[:timeout]) if all_agents_cfg[:timeout]
|
|
96
|
+
parameters(all_agents_cfg[:parameters]) if all_agents_cfg[:parameters]
|
|
97
|
+
headers(all_agents_cfg[:headers]) if all_agents_cfg[:headers]
|
|
98
|
+
coding_agent(all_agents_cfg[:coding_agent]) unless all_agents_cfg[:coding_agent].nil?
|
|
99
|
+
disable_default_tools(all_agents_cfg[:disable_default_tools]) unless all_agents_cfg[:disable_default_tools].nil?
|
|
100
|
+
|
|
101
|
+
if all_agents_hks.any?
|
|
102
|
+
all_agents_hks.each do |event, hook_specs|
|
|
103
|
+
Array(hook_specs).each do |spec|
|
|
104
|
+
matcher = spec[:matcher]
|
|
105
|
+
hook(event, matcher: matcher, command: spec[:command], timeout: spec[:timeout]) if spec[:type] == "command"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
self.permissions_hash = all_agents_cfg[:permissions] if all_agents_cfg[:permissions]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def translate_agents(builder)
|
|
115
|
+
@parser.agents.each do |name, agent_config|
|
|
116
|
+
translate_agent(builder, name, agent_config)
|
|
117
|
+
rescue ConfigurationError => e
|
|
118
|
+
raise ConfigurationError, "Error in #{@parser.config_type}.agents.#{name}: #{e.message}"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def translate_agent(builder, name, config)
|
|
123
|
+
if config[:agent_file]
|
|
124
|
+
agent_file_path = resolve_agent_file_path(config[:agent_file])
|
|
125
|
+
|
|
126
|
+
unless File.exist?(agent_file_path)
|
|
127
|
+
raise ConfigurationError, "Agent file not found: #{agent_file_path}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
content = File.read(agent_file_path)
|
|
131
|
+
overrides = config.except(:agent_file)
|
|
132
|
+
|
|
133
|
+
if overrides.any?
|
|
134
|
+
builder.agent(name, content, &create_agent_config_block(overrides))
|
|
135
|
+
else
|
|
136
|
+
builder.agent(name, content)
|
|
137
|
+
end
|
|
138
|
+
else
|
|
139
|
+
builder.agent(name, &create_agent_config_block(config))
|
|
140
|
+
end
|
|
141
|
+
rescue StandardError => e
|
|
142
|
+
raise ConfigurationError, "Error loading agent '#{name}': #{e.message}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def create_agent_config_block(config)
|
|
146
|
+
proc do
|
|
147
|
+
description(config[:description]) if config[:description]
|
|
148
|
+
model(config[:model]) if config[:model]
|
|
149
|
+
provider(config[:provider]) if config[:provider]
|
|
150
|
+
base_url(config[:base_url]) if config[:base_url]
|
|
151
|
+
api_version(config[:api_version]) if config[:api_version]
|
|
152
|
+
context_window(config[:context_window]) if config[:context_window]
|
|
153
|
+
system_prompt(config[:system_prompt]) if config[:system_prompt]
|
|
154
|
+
directory(config[:directory]) if config[:directory]
|
|
155
|
+
timeout(config[:timeout]) if config[:timeout]
|
|
156
|
+
parameters(config[:parameters]) if config[:parameters]
|
|
157
|
+
headers(config[:headers]) if config[:headers]
|
|
158
|
+
coding_agent(config[:coding_agent]) unless config[:coding_agent].nil?
|
|
159
|
+
bypass_permissions(config[:bypass_permissions]) if config[:bypass_permissions]
|
|
160
|
+
disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
|
|
161
|
+
shared_across_delegations(config[:shared_across_delegations]) unless config[:shared_across_delegations].nil?
|
|
162
|
+
|
|
163
|
+
if config[:tools]&.any?
|
|
164
|
+
tool_names = config[:tools].map { |t| t.is_a?(Hash) ? t[:name] : t }
|
|
165
|
+
tools(*tool_names)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
delegates_to(*config[:delegates_to]) if config[:delegates_to]&.any?
|
|
169
|
+
|
|
170
|
+
config[:mcp_servers]&.each do |server|
|
|
171
|
+
mcp_server(server[:name], **server.except(:name))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
config[:hooks]&.each do |event, hook_specs|
|
|
175
|
+
Array(hook_specs).each do |spec|
|
|
176
|
+
matcher = spec[:matcher]
|
|
177
|
+
hook(event, matcher: matcher, command: spec[:command], timeout: spec[:timeout]) if spec[:type] == "command"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Translate context_management YAML config to DSL
|
|
182
|
+
if config[:context_management]
|
|
183
|
+
ctx_mgmt_config = config[:context_management]
|
|
184
|
+
context_management do
|
|
185
|
+
ctx_mgmt_config.each do |event_name, handler_cfg|
|
|
186
|
+
# Capture handler_cfg in closure for each threshold
|
|
187
|
+
captured_config = handler_cfg
|
|
188
|
+
on(event_name.to_sym) do |ctx|
|
|
189
|
+
action = captured_config[:action]&.to_s
|
|
190
|
+
|
|
191
|
+
case action
|
|
192
|
+
when "compress_tool_results"
|
|
193
|
+
ctx.compress_tool_results(
|
|
194
|
+
keep_recent: captured_config[:keep_recent] || 10,
|
|
195
|
+
truncate_to: captured_config[:truncate_to] || 200,
|
|
196
|
+
)
|
|
197
|
+
when "prune_old_messages"
|
|
198
|
+
ctx.prune_old_messages(keep_recent: captured_config[:keep_recent] || 20)
|
|
199
|
+
when "log_warning"
|
|
200
|
+
ctx.log_action("threshold_warning", threshold: ctx.threshold)
|
|
201
|
+
else
|
|
202
|
+
raise ConfigurationError, "Unknown context_management action: #{action}"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Let plugins handle their YAML config translation
|
|
210
|
+
# This removes SDK knowledge of plugin-specific configuration
|
|
211
|
+
PluginRegistry.all.each do |plugin|
|
|
212
|
+
plugin.translate_yaml_config(self, config)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
self.permissions_hash = config[:permissions] if config[:permissions]
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def translate_nodes(builder)
|
|
220
|
+
@parser.nodes.each do |node_name, node_config|
|
|
221
|
+
builder.node(node_name) do
|
|
222
|
+
node_config[:agents]&.each do |agent_config|
|
|
223
|
+
agent_name = agent_config[:agent].to_sym
|
|
224
|
+
delegates = agent_config[:delegates_to] || []
|
|
225
|
+
reset_ctx = agent_config.key?(:reset_context) ? agent_config[:reset_context] : true
|
|
226
|
+
tools_override = agent_config[:tools]
|
|
227
|
+
|
|
228
|
+
agent_cfg = agent(agent_name, reset_context: reset_ctx)
|
|
229
|
+
agent_cfg = agent_cfg.delegates_to(*delegates) if delegates.any?
|
|
230
|
+
agent_cfg.tools(*tools_override) if tools_override
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
depends_on(*node_config[:dependencies]) if node_config[:dependencies]&.any?
|
|
234
|
+
|
|
235
|
+
lead(node_config[:lead].to_sym) if node_config[:lead]
|
|
236
|
+
|
|
237
|
+
if node_config[:input_command]
|
|
238
|
+
input_command(node_config[:input_command], timeout: node_config[:input_timeout] || 60)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
if node_config[:output_command]
|
|
242
|
+
output_command(node_config[:output_command], timeout: node_config[:output_timeout] || 60)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def resolve_agent_file_path(file_path)
|
|
249
|
+
return file_path if Pathname.new(file_path).absolute?
|
|
250
|
+
|
|
251
|
+
@parser.base_dir.join(file_path).to_s
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|