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,397 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- class Configuration
5
- # Handles YAML parsing, validation, and normalization
6
- #
7
- # This class is responsible for:
8
- # - Loading and parsing YAML content
9
- # - Validating configuration structure
10
- # - Normalizing data (symbolizing keys, env interpolation)
11
- # - Detecting configuration type (swarm vs workflow)
12
- # - Loading agents and nodes
13
- # - Detecting circular dependencies
14
- #
15
- # After parsing, the parsed data can be translated to a Swarm/Workflow
16
- # using the Translator class.
17
- class Parser
18
- ENV_VAR_WITH_DEFAULT_PATTERN = /\$\{([^:}]+)(:=([^}]*))?\}/
19
-
20
- attr_reader :config_type,
21
- :swarm_name,
22
- :swarm_id,
23
- :lead_agent,
24
- :start_node,
25
- :agents,
26
- :all_agents_config,
27
- :swarm_hooks,
28
- :all_agents_hooks,
29
- :scratchpad_mode,
30
- :nodes,
31
- :external_swarms,
32
- :execution_timeout
33
-
34
- # Initialize parser with YAML content and options
35
- #
36
- # @param yaml_content [String] YAML configuration content
37
- # @param base_dir [String, Pathname] Base directory for resolving paths
38
- # @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
39
- # When nil, uses the global SwarmSDK.config.env_interpolation setting.
40
- # When true, interpolates ${VAR} and ${VAR:=default} patterns.
41
- # When false, skips interpolation entirely.
42
- def initialize(yaml_content, base_dir:, env_interpolation: nil)
43
- @yaml_content = yaml_content
44
- @base_dir = Pathname.new(base_dir).expand_path
45
- @env_interpolation = env_interpolation
46
- @config_type = nil
47
- @swarm_id = nil
48
- @swarm_name = nil
49
- @lead_agent = nil
50
- @start_node = nil
51
- @agents = {}
52
- @all_agents_config = {}
53
- @swarm_hooks = {}
54
- @all_agents_hooks = {}
55
- @external_swarms = {}
56
- @nodes = {}
57
- @scratchpad_mode = :disabled
58
- @execution_timeout = nil
59
- end
60
-
61
- def parse
62
- @config = YAML.safe_load(@yaml_content, permitted_classes: [Symbol], aliases: true)
63
-
64
- unless @config.is_a?(Hash)
65
- raise ConfigurationError, "Invalid YAML syntax: configuration must be a Hash"
66
- end
67
-
68
- @config = Utils.symbolize_keys(@config)
69
- interpolate_env_vars!(@config) if env_interpolation_enabled?
70
-
71
- validate_version
72
- detect_and_validate_type
73
- load_common_config
74
- load_type_specific_config
75
- load_agents
76
- load_nodes if @config_type == :workflow
77
- detect_circular_dependencies
78
-
79
- self
80
- rescue Psych::SyntaxError => e
81
- raise ConfigurationError, "Invalid YAML syntax: #{e.message}"
82
- end
83
-
84
- def agent_names
85
- @agents.keys
86
- end
87
-
88
- def connections_for(agent_name)
89
- agent_config = @agents[agent_name]
90
- return [] unless agent_config
91
-
92
- delegates = agent_config[:delegates_to] || []
93
-
94
- # Handle both array and hash formats for delegates_to
95
- case delegates
96
- when Array
97
- # Array of symbols: [:frontend, :backend]
98
- # OR array of hashes: [{agent: :frontend, tool_name: "Custom"}]
99
- delegates.map do |item|
100
- case item
101
- when Symbol, String
102
- item.to_sym
103
- when Hash
104
- # Extract agent name from hash format
105
- agent_name = item[:agent] || item["agent"]
106
- agent_name&.to_sym
107
- end
108
- end.compact # Remove nils from malformed hashes
109
- when Hash
110
- # Hash format: {frontend: "Custom", backend: nil}
111
- delegates.keys.map(&:to_sym)
112
- else
113
- []
114
- end
115
- end
116
-
117
- attr_reader :base_dir
118
-
119
- private
120
-
121
- # Check if environment variable interpolation is enabled
122
- #
123
- # Uses the local setting if explicitly set, otherwise falls back to global config.
124
- #
125
- # @return [Boolean] true if interpolation should be performed
126
- def env_interpolation_enabled?
127
- return @env_interpolation unless @env_interpolation.nil?
128
-
129
- SwarmSDK.config.env_interpolation
130
- end
131
-
132
- def validate_version
133
- version = @config[:version]
134
- raise ConfigurationError, "Missing 'version' field in configuration" unless version
135
- raise ConfigurationError, "SwarmSDK requires version: 2 configuration. Got version: #{version}" unless version == 2
136
- end
137
-
138
- def detect_and_validate_type
139
- has_swarm = @config.key?(:swarm)
140
- has_workflow = @config.key?(:workflow)
141
-
142
- if has_swarm && has_workflow
143
- raise ConfigurationError, "Cannot have both 'swarm:' and 'workflow:' keys. Use one or the other."
144
- end
145
-
146
- unless has_swarm || has_workflow
147
- raise ConfigurationError, "Missing 'swarm:' or 'workflow:' key in configuration"
148
- end
149
-
150
- @config_type = has_swarm ? :swarm : :workflow
151
- @root_config = @config[@config_type]
152
- end
153
-
154
- def load_common_config
155
- raise ConfigurationError, "Missing 'name' field in #{@config_type} configuration" unless @root_config[:name]
156
-
157
- @swarm_name = @root_config[:name]
158
- @swarm_id = @root_config[:id]
159
- @scratchpad_mode = parse_scratchpad_mode(@root_config[:scratchpad])
160
- @execution_timeout = @root_config[:execution_timeout]
161
-
162
- load_all_agents_config
163
- load_hooks_config
164
- load_external_swarms(@root_config[:swarms]) if @root_config[:swarms]
165
- end
166
-
167
- def load_type_specific_config
168
- if @config_type == :swarm
169
- load_swarm_config
170
- else
171
- load_workflow_config
172
- end
173
- end
174
-
175
- def load_swarm_config
176
- raise ConfigurationError, "Missing 'lead' field in swarm configuration" unless @root_config[:lead]
177
- raise ConfigurationError, "Missing 'agents' field in swarm configuration" unless @root_config[:agents]
178
-
179
- @lead_agent = @root_config[:lead].to_sym
180
-
181
- if @root_config[:nodes] || @root_config[:start_node]
182
- raise ConfigurationError, "Swarm configuration cannot have 'nodes' or 'start_node'. Use 'workflow:' key instead."
183
- end
184
- end
185
-
186
- def load_workflow_config
187
- raise ConfigurationError, "Missing 'start_node' field in workflow configuration" unless @root_config[:start_node]
188
- raise ConfigurationError, "Missing 'nodes' field in workflow configuration" unless @root_config[:nodes]
189
- raise ConfigurationError, "Missing 'agents' field in workflow configuration" unless @root_config[:agents]
190
-
191
- @start_node = @root_config[:start_node].to_sym
192
-
193
- if @root_config[:lead]
194
- raise ConfigurationError, "Workflow configuration cannot have 'lead'. Use 'start_node' instead."
195
- end
196
- end
197
-
198
- def load_all_agents_config
199
- @all_agents_config = @root_config[:all_agents] || {}
200
-
201
- if @all_agents_config[:disable_default_tools].is_a?(Array)
202
- @all_agents_config[:disable_default_tools] = @all_agents_config[:disable_default_tools].map(&:to_sym)
203
- end
204
- end
205
-
206
- def load_hooks_config
207
- @swarm_hooks = Utils.symbolize_keys(@root_config[:hooks] || {})
208
-
209
- if @root_config[:all_agents]
210
- @all_agents_hooks = Utils.symbolize_keys(@root_config[:all_agents][:hooks] || {})
211
- end
212
- end
213
-
214
- def load_external_swarms(swarms_config)
215
- @external_swarms = {}
216
- swarms_config.each do |name, config|
217
- source = if config[:file]
218
- file_path = if config[:file].start_with?("/")
219
- config[:file]
220
- else
221
- (@base_dir / config[:file]).to_s
222
- end
223
- { type: :file, value: file_path }
224
- elsif config[:yaml]
225
- { type: :yaml, value: config[:yaml] }
226
- elsif config[:swarm]
227
- inline_config = {
228
- version: 2,
229
- swarm: config[:swarm],
230
- }
231
- yaml_string = Utils.hash_to_yaml(inline_config)
232
- { type: :yaml, value: yaml_string }
233
- else
234
- raise ConfigurationError, "Swarm '#{name}' must specify either 'file:', 'yaml:', or 'swarm:' (inline definition)"
235
- end
236
-
237
- @external_swarms[name.to_sym] = {
238
- source: source,
239
- keep_context: config.fetch(:keep_context, true),
240
- }
241
- end
242
- end
243
-
244
- def load_agents
245
- swarm_agents = @root_config[:agents]
246
- raise ConfigurationError, "No agents defined" if swarm_agents.empty?
247
-
248
- swarm_agents.each do |name, agent_config|
249
- parsed_config = if agent_config.nil?
250
- {}
251
- elsif agent_config.is_a?(String)
252
- { agent_file: agent_config }
253
- elsif agent_config.is_a?(Hash) && agent_config[:agent_file]
254
- agent_config
255
- else
256
- agent_config || {}
257
- end
258
-
259
- if parsed_config[:agent_file].nil? && parsed_config[:description].nil?
260
- raise ConfigurationError,
261
- "Agent '#{name}' missing required 'description' field"
262
- end
263
-
264
- @agents[name] = parsed_config
265
- end
266
-
267
- if @config_type == :swarm
268
- unless @agents.key?(@lead_agent)
269
- raise ConfigurationError, "Lead agent '#{@lead_agent}' not found in agents"
270
- end
271
- end
272
- end
273
-
274
- def load_nodes
275
- @nodes = Utils.symbolize_keys(@root_config[:nodes])
276
-
277
- unless @nodes.key?(@start_node)
278
- raise ConfigurationError, "start_node '#{@start_node}' not found in nodes"
279
- end
280
-
281
- @nodes.each do |node_name, node_config|
282
- unless node_config.is_a?(Hash)
283
- raise ConfigurationError, "Node '#{node_name}' must be a hash"
284
- end
285
-
286
- if node_config[:agents]
287
- unless node_config[:agents].is_a?(Array)
288
- raise ConfigurationError, "Node '#{node_name}' agents must be an array"
289
- end
290
-
291
- node_config[:agents].each do |agent_config|
292
- unless agent_config.is_a?(Hash) && agent_config[:agent]
293
- raise ConfigurationError,
294
- "Node '#{node_name}' agents must be hashes with 'agent' key"
295
- end
296
-
297
- agent_sym = agent_config[:agent].to_sym
298
- unless @agents.key?(agent_sym)
299
- raise ConfigurationError,
300
- "Node '#{node_name}' references undefined agent '#{agent_config[:agent]}'"
301
- end
302
- end
303
- end
304
-
305
- next unless node_config[:dependencies]
306
- unless node_config[:dependencies].is_a?(Array)
307
- raise ConfigurationError, "Node '#{node_name}' dependencies must be an array"
308
- end
309
-
310
- node_config[:dependencies].each do |dep|
311
- dep_sym = dep.to_sym
312
- unless @nodes.key?(dep_sym)
313
- raise ConfigurationError,
314
- "Node '#{node_name}' depends on undefined node '#{dep}'"
315
- end
316
- end
317
- end
318
- end
319
-
320
- def parse_scratchpad_mode(value)
321
- return :disabled if value.nil?
322
-
323
- value = value.to_sym if value.is_a?(String)
324
-
325
- case value
326
- when :enabled, :disabled, :per_node
327
- value
328
- else
329
- raise ConfigurationError,
330
- "Invalid scratchpad mode: #{value.inspect}. Use :enabled, :per_node, or :disabled"
331
- end
332
- end
333
-
334
- def interpolate_env_vars!(obj)
335
- case obj
336
- when String
337
- interpolate_env_string(obj)
338
- when Hash
339
- obj.transform_values! { |v| interpolate_env_vars!(v) }
340
- when Array
341
- obj.map! { |v| interpolate_env_vars!(v) }
342
- else
343
- obj
344
- end
345
- end
346
-
347
- def interpolate_env_string(str)
348
- str.gsub(ENV_VAR_WITH_DEFAULT_PATTERN) do |_match|
349
- env_var = Regexp.last_match(1)
350
- has_default = Regexp.last_match(2)
351
- default_value = Regexp.last_match(3)
352
-
353
- if ENV.key?(env_var)
354
- ENV[env_var]
355
- elsif has_default
356
- default_value || ""
357
- else
358
- raise ConfigurationError, "Environment variable '#{env_var}' is not set"
359
- end
360
- end
361
- end
362
-
363
- def detect_circular_dependencies
364
- @agents.each_key do |agent_name|
365
- visited = Set.new
366
- path = []
367
- detect_cycle_from(agent_name, visited, path)
368
- end
369
- end
370
-
371
- def detect_cycle_from(agent_name, visited, path)
372
- return if visited.include?(agent_name)
373
-
374
- if path.include?(agent_name)
375
- cycle_start = path.index(agent_name)
376
- cycle = path[cycle_start..] + [agent_name]
377
- raise CircularDependencyError, "Circular dependency detected: #{cycle.join(" -> ")}"
378
- end
379
-
380
- path.push(agent_name)
381
- connections_for(agent_name).each do |connection|
382
- connection_sym = connection.to_sym
383
-
384
- next if @external_swarms.key?(connection_sym)
385
-
386
- unless @agents.key?(connection_sym)
387
- raise ConfigurationError, "Agent '#{agent_name}' delegates to unknown target '#{connection}' (not a local agent or registered swarm)"
388
- end
389
-
390
- detect_cycle_from(connection_sym, visited, path)
391
- end
392
- path.pop
393
- visited.add(agent_name)
394
- end
395
- end
396
- end
397
- end
@@ -1,285 +0,0 @@
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
- builder.execution_timeout(@parser.execution_timeout) if @parser.execution_timeout
44
-
45
- if @parser.external_swarms&.any?
46
- external_swarms = @parser.external_swarms
47
- builder.swarms do
48
- external_swarms.each do |name, config|
49
- source = config[:source]
50
- case source[:type]
51
- when :file
52
- register(name, file: source[:value], keep_context: config[:keep_context])
53
- when :yaml
54
- register(name, yaml: source[:value], keep_context: config[:keep_context])
55
- else
56
- raise ConfigurationError, "Unknown source type: #{source[:type]}"
57
- end
58
- end
59
- end
60
- end
61
-
62
- translate_all_agents(builder) if @parser.all_agents_config.any?
63
- end
64
-
65
- def translate_type_specific_config(builder)
66
- if @parser.config_type == :swarm
67
- builder.lead(@parser.lead_agent)
68
- else
69
- builder.start_node(@parser.start_node)
70
- translate_nodes(builder)
71
- end
72
- end
73
-
74
- def translate_hooks(builder)
75
- return if @parser.swarm_hooks.none?
76
-
77
- @parser.swarm_hooks.each do |event, hook_specs|
78
- Array(hook_specs).each do |spec|
79
- if spec[:type] == "command"
80
- builder.hook(event, command: spec[:command], timeout: spec[:timeout])
81
- end
82
- end
83
- end
84
- end
85
-
86
- def translate_all_agents(builder)
87
- all_agents_cfg = @parser.all_agents_config
88
- all_agents_hks = @parser.all_agents_hooks
89
-
90
- builder.all_agents do
91
- tools(*all_agents_cfg[:tools]) if all_agents_cfg[:tools]&.any?
92
- model(all_agents_cfg[:model]) if all_agents_cfg[:model]
93
- provider(all_agents_cfg[:provider]) if all_agents_cfg[:provider]
94
- base_url(all_agents_cfg[:base_url]) if all_agents_cfg[:base_url]
95
- api_version(all_agents_cfg[:api_version]) if all_agents_cfg[:api_version]
96
- request_timeout(all_agents_cfg[:request_timeout]) if all_agents_cfg[:request_timeout]
97
- turn_timeout(all_agents_cfg[:turn_timeout]) if all_agents_cfg[:turn_timeout]
98
- parameters(all_agents_cfg[:parameters]) if all_agents_cfg[:parameters]
99
- headers(all_agents_cfg[:headers]) if all_agents_cfg[:headers]
100
- coding_agent(all_agents_cfg[:coding_agent]) unless all_agents_cfg[:coding_agent].nil?
101
- disable_default_tools(all_agents_cfg[:disable_default_tools]) unless all_agents_cfg[:disable_default_tools].nil?
102
- streaming(all_agents_cfg[:streaming]) unless all_agents_cfg[:streaming].nil?
103
- disable_environment_info(all_agents_cfg[:disable_environment_info]) unless all_agents_cfg[:disable_environment_info].nil?
104
- thinking(**all_agents_cfg[:thinking]) if all_agents_cfg[:thinking]
105
-
106
- if all_agents_hks.any?
107
- all_agents_hks.each do |event, hook_specs|
108
- Array(hook_specs).each do |spec|
109
- matcher = spec[:matcher]
110
- hook(event, matcher: matcher, command: spec[:command], timeout: spec[:timeout]) if spec[:type] == "command"
111
- end
112
- end
113
- end
114
-
115
- self.permissions_hash = all_agents_cfg[:permissions] if all_agents_cfg[:permissions]
116
- end
117
- end
118
-
119
- def translate_agents(builder)
120
- @parser.agents.each do |name, agent_config|
121
- translate_agent(builder, name, agent_config)
122
- rescue ConfigurationError => e
123
- raise ConfigurationError, "Error in #{@parser.config_type}.agents.#{name}: #{e.message}"
124
- end
125
- end
126
-
127
- def translate_agent(builder, name, config)
128
- if config[:agent_file]
129
- agent_file_path = resolve_agent_file_path(config[:agent_file])
130
-
131
- unless File.exist?(agent_file_path)
132
- raise ConfigurationError, "Agent file not found: #{agent_file_path}"
133
- end
134
-
135
- content = File.read(agent_file_path)
136
- overrides = config.except(:agent_file)
137
-
138
- if overrides.any?
139
- builder.agent(name, content, &create_agent_config_block(overrides))
140
- else
141
- builder.agent(name, content)
142
- end
143
- else
144
- builder.agent(name, &create_agent_config_block(config))
145
- end
146
- rescue StandardError => e
147
- raise ConfigurationError, "Error loading agent '#{name}': #{e.message}"
148
- end
149
-
150
- def create_agent_config_block(config)
151
- proc do
152
- description(config[:description]) if config[:description]
153
- model(config[:model]) if config[:model]
154
- provider(config[:provider]) if config[:provider]
155
- base_url(config[:base_url]) if config[:base_url]
156
- api_version(config[:api_version]) if config[:api_version]
157
- context_window(config[:context_window]) if config[:context_window]
158
- system_prompt(config[:system_prompt]) if config[:system_prompt]
159
- directory(config[:directory]) if config[:directory]
160
- request_timeout(config[:request_timeout]) if config[:request_timeout]
161
- turn_timeout(config[:turn_timeout]) if config[:turn_timeout]
162
- parameters(config[:parameters]) if config[:parameters]
163
- headers(config[:headers]) if config[:headers]
164
- coding_agent(config[:coding_agent]) unless config[:coding_agent].nil?
165
- bypass_permissions(config[:bypass_permissions]) if config[:bypass_permissions]
166
- disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
167
- shared_across_delegations(config[:shared_across_delegations]) unless config[:shared_across_delegations].nil?
168
- streaming(config[:streaming]) unless config[:streaming].nil?
169
- disable_environment_info(config[:disable_environment_info]) unless config[:disable_environment_info].nil?
170
- thinking(**config[:thinking]) if config[:thinking]
171
-
172
- if config[:tools]&.any?
173
- tool_names = config[:tools].map { |t| t.is_a?(Hash) ? t[:name] : t }
174
- tools(*tool_names)
175
- end
176
-
177
- # Handle both array and hash formats for delegates_to
178
- if config[:delegates_to]&.any?
179
- delegation_config = config[:delegates_to]
180
- if delegation_config.is_a?(Hash)
181
- # Hash format: { frontend: "Custom", backend: nil }
182
- # Pass as single hash argument, not splatted
183
- delegates_to(delegation_config)
184
- elsif delegation_config.is_a?(Array)
185
- # Array format: [:frontend, :backend] OR [{agent: :frontend, tool_name: "Custom"}]
186
- # Splat the array
187
- delegates_to(*delegation_config)
188
- end
189
- end
190
-
191
- config[:mcp_servers]&.each do |server|
192
- mcp_server(server[:name], **server.except(:name))
193
- end
194
-
195
- config[:hooks]&.each do |event, hook_specs|
196
- Array(hook_specs).each do |spec|
197
- matcher = spec[:matcher]
198
- hook(event, matcher: matcher, command: spec[:command], timeout: spec[:timeout]) if spec[:type] == "command"
199
- end
200
- end
201
-
202
- # Translate context_management YAML config to DSL
203
- if config[:context_management]
204
- ctx_mgmt_config = config[:context_management]
205
- context_management do
206
- ctx_mgmt_config.each do |event_name, handler_cfg|
207
- # Capture handler_cfg in closure for each threshold
208
- captured_config = handler_cfg
209
- on(event_name.to_sym) do |ctx|
210
- action = captured_config[:action]&.to_s
211
-
212
- case action
213
- when "compress_tool_results"
214
- ctx.compress_tool_results(
215
- keep_recent: captured_config[:keep_recent] || 10,
216
- truncate_to: captured_config[:truncate_to] || 200,
217
- )
218
- when "prune_old_messages"
219
- ctx.prune_old_messages(keep_recent: captured_config[:keep_recent] || 20)
220
- when "log_warning"
221
- ctx.log_action("threshold_warning", threshold: ctx.threshold)
222
- else
223
- raise ConfigurationError, "Unknown context_management action: #{action}"
224
- end
225
- end
226
- end
227
- end
228
- end
229
-
230
- # Let plugins handle their YAML config translation
231
- # This removes SDK knowledge of plugin-specific configuration
232
- PluginRegistry.all.each do |plugin|
233
- plugin.translate_yaml_config(self, config)
234
- end
235
-
236
- self.permissions_hash = config[:permissions] if config[:permissions]
237
- end
238
- end
239
-
240
- def translate_nodes(builder)
241
- @parser.nodes.each do |node_name, node_config|
242
- builder.node(node_name) do
243
- node_config[:agents]&.each do |agent_config|
244
- agent_name = agent_config[:agent].to_sym
245
- delegates = agent_config[:delegates_to] || []
246
- reset_ctx = agent_config.key?(:reset_context) ? agent_config[:reset_context] : true
247
- tools_override = agent_config[:tools]
248
-
249
- agent_cfg = agent(agent_name, reset_context: reset_ctx)
250
-
251
- # Handle both array and hash formats for delegates_to
252
- if delegates.any?
253
- if delegates.is_a?(Hash)
254
- agent_cfg = agent_cfg.delegates_to(delegates)
255
- elsif delegates.is_a?(Array)
256
- agent_cfg = agent_cfg.delegates_to(*delegates)
257
- end
258
- end
259
-
260
- agent_cfg.tools(*tools_override) if tools_override
261
- end
262
-
263
- depends_on(*node_config[:dependencies]) if node_config[:dependencies]&.any?
264
-
265
- lead(node_config[:lead].to_sym) if node_config[:lead]
266
-
267
- if node_config[:input_command]
268
- input_command(node_config[:input_command], timeout: node_config[:input_timeout] || 60)
269
- end
270
-
271
- if node_config[:output_command]
272
- output_command(node_config[:output_command], timeout: node_config[:output_timeout] || 60)
273
- end
274
- end
275
- end
276
- end
277
-
278
- def resolve_agent_file_path(file_path)
279
- return file_path if Pathname.new(file_path).absolute?
280
-
281
- @parser.base_dir.join(file_path).to_s
282
- end
283
- end
284
- end
285
- end