swarm_sdk 2.7.14 → 3.0.0.alpha2

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 (185) 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/document_converters/base.rb +84 -0
  42. data/lib/swarm_sdk/v3/tools/document_converters/docx_converter.rb +120 -0
  43. data/lib/swarm_sdk/v3/tools/document_converters/pdf_converter.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/document_converters/xlsx_converter.rb +128 -0
  45. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  46. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  47. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  48. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  49. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  50. data/lib/swarm_sdk/v3/tools/read.rb +213 -0
  51. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  52. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  53. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  54. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  55. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  56. data/lib/swarm_sdk/v3.rb +145 -0
  57. metadata +88 -149
  58. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  59. data/lib/swarm_sdk/agent/builder.rb +0 -705
  60. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  61. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  62. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  63. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  64. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  65. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  66. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  67. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  68. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  69. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  70. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  71. data/lib/swarm_sdk/agent/context.rb +0 -115
  72. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  73. data/lib/swarm_sdk/agent/definition.rb +0 -588
  74. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  75. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  76. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  77. data/lib/swarm_sdk/agent_registry.rb +0 -146
  78. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  79. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  80. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  81. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  82. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  83. data/lib/swarm_sdk/config.rb +0 -368
  84. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  85. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  86. data/lib/swarm_sdk/configuration.rb +0 -165
  87. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  88. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  89. data/lib/swarm_sdk/context_compactor.rb +0 -335
  90. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  91. data/lib/swarm_sdk/context_management/context.rb +0 -328
  92. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  93. data/lib/swarm_sdk/defaults.rb +0 -251
  94. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  95. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  96. data/lib/swarm_sdk/hooks/context.rb +0 -197
  97. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  98. data/lib/swarm_sdk/hooks/error.rb +0 -29
  99. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  100. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  101. data/lib/swarm_sdk/hooks/result.rb +0 -150
  102. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  103. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  104. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  105. data/lib/swarm_sdk/log_collector.rb +0 -227
  106. data/lib/swarm_sdk/log_stream.rb +0 -127
  107. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  108. data/lib/swarm_sdk/model_aliases.json +0 -8
  109. data/lib/swarm_sdk/models.json +0 -44002
  110. data/lib/swarm_sdk/models.rb +0 -161
  111. data/lib/swarm_sdk/node_context.rb +0 -245
  112. data/lib/swarm_sdk/observer/builder.rb +0 -81
  113. data/lib/swarm_sdk/observer/config.rb +0 -45
  114. data/lib/swarm_sdk/observer/manager.rb +0 -248
  115. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  116. data/lib/swarm_sdk/permissions/config.rb +0 -239
  117. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  118. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  119. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  120. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  121. data/lib/swarm_sdk/plugin.rb +0 -309
  122. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  123. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  124. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  125. data/lib/swarm_sdk/restore_result.rb +0 -65
  126. data/lib/swarm_sdk/result.rb +0 -241
  127. data/lib/swarm_sdk/snapshot.rb +0 -156
  128. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  129. data/lib/swarm_sdk/state_restorer.rb +0 -476
  130. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  131. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  132. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  133. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  134. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  135. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  136. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  141. data/lib/swarm_sdk/swarm.rb +0 -973
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/base.rb +0 -63
  145. data/lib/swarm_sdk/tools/bash.rb +0 -280
  146. data/lib/swarm_sdk/tools/clock.rb +0 -46
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  160. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  161. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  162. data/lib/swarm_sdk/tools/read.rb +0 -261
  163. data/lib/swarm_sdk/tools/registry.rb +0 -205
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  166. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  167. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  168. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  169. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  170. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  171. data/lib/swarm_sdk/tools/think.rb +0 -100
  172. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  173. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  174. data/lib/swarm_sdk/tools/write.rb +0 -112
  175. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  176. data/lib/swarm_sdk/utils.rb +0 -68
  177. data/lib/swarm_sdk/validation_result.rb +0 -33
  178. data/lib/swarm_sdk/version.rb +0 -5
  179. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  180. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  181. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  182. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  183. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  184. data/lib/swarm_sdk/workflow.rb +0 -589
  185. 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