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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/agent/builder.rb +58 -0
  3. data/lib/swarm_sdk/agent/chat.rb +527 -1059
  4. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
  5. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  6. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
  7. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  8. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +262 -0
  9. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
  10. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  11. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +11 -13
  12. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  13. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
  14. data/lib/swarm_sdk/agent/context.rb +1 -2
  15. data/lib/swarm_sdk/agent/definition.rb +66 -154
  16. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
  17. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  18. data/lib/swarm_sdk/builders/base_builder.rb +409 -0
  19. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  20. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  21. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  22. data/lib/swarm_sdk/config.rb +301 -0
  23. data/lib/swarm_sdk/configuration/parser.rb +353 -0
  24. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  25. data/lib/swarm_sdk/configuration.rb +65 -543
  26. data/lib/swarm_sdk/context_compactor/token_counter.rb +2 -6
  27. data/lib/swarm_sdk/context_compactor.rb +6 -11
  28. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  29. data/lib/swarm_sdk/context_management/context.rb +328 -0
  30. data/lib/swarm_sdk/defaults.rb +196 -0
  31. data/lib/swarm_sdk/events_to_messages.rb +18 -0
  32. data/lib/swarm_sdk/hooks/adapter.rb +3 -3
  33. data/lib/swarm_sdk/hooks/shell_executor.rb +4 -2
  34. data/lib/swarm_sdk/log_collector.rb +179 -29
  35. data/lib/swarm_sdk/log_stream.rb +29 -0
  36. data/lib/swarm_sdk/models.json +4333 -1
  37. data/lib/swarm_sdk/node_context.rb +1 -1
  38. data/lib/swarm_sdk/observer/builder.rb +81 -0
  39. data/lib/swarm_sdk/observer/config.rb +45 -0
  40. data/lib/swarm_sdk/observer/manager.rb +236 -0
  41. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  42. data/lib/swarm_sdk/plugin.rb +93 -3
  43. data/lib/swarm_sdk/snapshot.rb +6 -6
  44. data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
  45. data/lib/swarm_sdk/state_restorer.rb +136 -151
  46. data/lib/swarm_sdk/state_snapshot.rb +65 -100
  47. data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
  48. data/lib/swarm_sdk/swarm/builder.rb +44 -578
  49. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  50. data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
  51. data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
  52. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  53. data/lib/swarm_sdk/swarm/tool_configurator.rb +44 -140
  54. data/lib/swarm_sdk/swarm.rb +146 -689
  55. data/lib/swarm_sdk/tools/bash.rb +14 -8
  56. data/lib/swarm_sdk/tools/delegate.rb +61 -43
  57. data/lib/swarm_sdk/tools/edit.rb +8 -13
  58. data/lib/swarm_sdk/tools/glob.rb +12 -4
  59. data/lib/swarm_sdk/tools/grep.rb +7 -0
  60. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  61. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  62. data/lib/swarm_sdk/tools/read.rb +16 -18
  63. data/lib/swarm_sdk/tools/registry.rb +122 -10
  64. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +9 -5
  65. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  66. data/lib/swarm_sdk/tools/todo_write.rb +7 -0
  67. data/lib/swarm_sdk/tools/web_fetch.rb +20 -17
  68. data/lib/swarm_sdk/tools/write.rb +8 -13
  69. data/lib/swarm_sdk/version.rb +1 -1
  70. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
  71. data/lib/swarm_sdk/workflow/builder.rb +143 -0
  72. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  73. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +7 -5
  74. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +5 -3
  75. data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
  76. data/lib/swarm_sdk.rb +64 -104
  77. metadata +68 -15
  78. 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