swarm_sdk 2.7.13 → 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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
  4. data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
  5. data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
  6. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  7. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  8. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  9. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  10. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  11. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  12. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  13. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  14. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  15. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  16. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  17. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  18. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  19. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  20. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  24. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  25. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  26. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  27. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  28. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  29. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  30. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  31. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  32. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  33. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  34. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  35. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  36. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  37. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  38. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  39. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  40. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  41. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  42. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  43. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  45. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  46. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  47. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  48. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  49. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  50. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  51. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  52. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  53. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  54. data/lib/swarm_sdk/v3.rb +145 -0
  55. metadata +84 -148
  56. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  57. data/lib/swarm_sdk/agent/builder.rb +0 -680
  58. data/lib/swarm_sdk/agent/chat.rb +0 -1432
  59. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  60. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  61. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  62. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  63. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  64. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  65. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  66. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  67. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  68. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  69. data/lib/swarm_sdk/agent/context.rb +0 -115
  70. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  71. data/lib/swarm_sdk/agent/definition.rb +0 -581
  72. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  73. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  74. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  75. data/lib/swarm_sdk/agent_registry.rb +0 -146
  76. data/lib/swarm_sdk/builders/base_builder.rb +0 -553
  77. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  78. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  79. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  80. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  81. data/lib/swarm_sdk/config.rb +0 -367
  82. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  83. data/lib/swarm_sdk/configuration/translator.rb +0 -283
  84. data/lib/swarm_sdk/configuration.rb +0 -165
  85. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  86. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  87. data/lib/swarm_sdk/context_compactor.rb +0 -335
  88. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  89. data/lib/swarm_sdk/context_management/context.rb +0 -328
  90. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  91. data/lib/swarm_sdk/defaults.rb +0 -251
  92. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  93. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  94. data/lib/swarm_sdk/hooks/context.rb +0 -197
  95. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  96. data/lib/swarm_sdk/hooks/error.rb +0 -29
  97. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  98. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  99. data/lib/swarm_sdk/hooks/result.rb +0 -150
  100. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  101. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  102. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  103. data/lib/swarm_sdk/log_collector.rb +0 -227
  104. data/lib/swarm_sdk/log_stream.rb +0 -127
  105. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  106. data/lib/swarm_sdk/model_aliases.json +0 -8
  107. data/lib/swarm_sdk/models.json +0 -44002
  108. data/lib/swarm_sdk/models.rb +0 -161
  109. data/lib/swarm_sdk/node_context.rb +0 -245
  110. data/lib/swarm_sdk/observer/builder.rb +0 -81
  111. data/lib/swarm_sdk/observer/config.rb +0 -45
  112. data/lib/swarm_sdk/observer/manager.rb +0 -236
  113. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  114. data/lib/swarm_sdk/permissions/config.rb +0 -239
  115. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  116. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  117. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  118. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  119. data/lib/swarm_sdk/plugin.rb +0 -309
  120. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  121. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  122. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  123. data/lib/swarm_sdk/restore_result.rb +0 -65
  124. data/lib/swarm_sdk/result.rb +0 -212
  125. data/lib/swarm_sdk/snapshot.rb +0 -156
  126. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  127. data/lib/swarm_sdk/state_restorer.rb +0 -476
  128. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  129. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  130. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
  131. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  132. data/lib/swarm_sdk/swarm/executor.rb +0 -290
  133. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
  134. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  135. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
  136. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
  137. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  138. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  139. data/lib/swarm_sdk/swarm.rb +0 -843
  140. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  141. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  142. data/lib/swarm_sdk/tools/base.rb +0 -63
  143. data/lib/swarm_sdk/tools/bash.rb +0 -280
  144. data/lib/swarm_sdk/tools/clock.rb +0 -46
  145. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  146. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  147. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  148. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  149. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  150. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  151. data/lib/swarm_sdk/tools/edit.rb +0 -145
  152. data/lib/swarm_sdk/tools/glob.rb +0 -166
  153. data/lib/swarm_sdk/tools/grep.rb +0 -235
  154. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  155. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  156. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  157. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -100
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  182. data/lib/swarm_sdk/workflow.rb +0 -589
  183. data/lib/swarm_sdk.rb +0 -718
@@ -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,283 +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
- thinking(**all_agents_cfg[:thinking]) if all_agents_cfg[:thinking]
104
-
105
- if all_agents_hks.any?
106
- all_agents_hks.each do |event, hook_specs|
107
- Array(hook_specs).each do |spec|
108
- matcher = spec[:matcher]
109
- hook(event, matcher: matcher, command: spec[:command], timeout: spec[:timeout]) if spec[:type] == "command"
110
- end
111
- end
112
- end
113
-
114
- self.permissions_hash = all_agents_cfg[:permissions] if all_agents_cfg[:permissions]
115
- end
116
- end
117
-
118
- def translate_agents(builder)
119
- @parser.agents.each do |name, agent_config|
120
- translate_agent(builder, name, agent_config)
121
- rescue ConfigurationError => e
122
- raise ConfigurationError, "Error in #{@parser.config_type}.agents.#{name}: #{e.message}"
123
- end
124
- end
125
-
126
- def translate_agent(builder, name, config)
127
- if config[:agent_file]
128
- agent_file_path = resolve_agent_file_path(config[:agent_file])
129
-
130
- unless File.exist?(agent_file_path)
131
- raise ConfigurationError, "Agent file not found: #{agent_file_path}"
132
- end
133
-
134
- content = File.read(agent_file_path)
135
- overrides = config.except(:agent_file)
136
-
137
- if overrides.any?
138
- builder.agent(name, content, &create_agent_config_block(overrides))
139
- else
140
- builder.agent(name, content)
141
- end
142
- else
143
- builder.agent(name, &create_agent_config_block(config))
144
- end
145
- rescue StandardError => e
146
- raise ConfigurationError, "Error loading agent '#{name}': #{e.message}"
147
- end
148
-
149
- def create_agent_config_block(config)
150
- proc do
151
- description(config[:description]) if config[:description]
152
- model(config[:model]) if config[:model]
153
- provider(config[:provider]) if config[:provider]
154
- base_url(config[:base_url]) if config[:base_url]
155
- api_version(config[:api_version]) if config[:api_version]
156
- context_window(config[:context_window]) if config[:context_window]
157
- system_prompt(config[:system_prompt]) if config[:system_prompt]
158
- directory(config[:directory]) if config[:directory]
159
- request_timeout(config[:request_timeout]) if config[:request_timeout]
160
- turn_timeout(config[:turn_timeout]) if config[:turn_timeout]
161
- parameters(config[:parameters]) if config[:parameters]
162
- headers(config[:headers]) if config[:headers]
163
- coding_agent(config[:coding_agent]) unless config[:coding_agent].nil?
164
- bypass_permissions(config[:bypass_permissions]) if config[:bypass_permissions]
165
- disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
166
- shared_across_delegations(config[:shared_across_delegations]) unless config[:shared_across_delegations].nil?
167
- streaming(config[:streaming]) unless config[:streaming].nil?
168
- thinking(**config[:thinking]) if config[:thinking]
169
-
170
- if config[:tools]&.any?
171
- tool_names = config[:tools].map { |t| t.is_a?(Hash) ? t[:name] : t }
172
- tools(*tool_names)
173
- end
174
-
175
- # Handle both array and hash formats for delegates_to
176
- if config[:delegates_to]&.any?
177
- delegation_config = config[:delegates_to]
178
- if delegation_config.is_a?(Hash)
179
- # Hash format: { frontend: "Custom", backend: nil }
180
- # Pass as single hash argument, not splatted
181
- delegates_to(delegation_config)
182
- elsif delegation_config.is_a?(Array)
183
- # Array format: [:frontend, :backend] OR [{agent: :frontend, tool_name: "Custom"}]
184
- # Splat the array
185
- delegates_to(*delegation_config)
186
- end
187
- end
188
-
189
- config[:mcp_servers]&.each do |server|
190
- mcp_server(server[:name], **server.except(:name))
191
- end
192
-
193
- config[:hooks]&.each do |event, hook_specs|
194
- Array(hook_specs).each do |spec|
195
- matcher = spec[:matcher]
196
- hook(event, matcher: matcher, command: spec[:command], timeout: spec[:timeout]) if spec[:type] == "command"
197
- end
198
- end
199
-
200
- # Translate context_management YAML config to DSL
201
- if config[:context_management]
202
- ctx_mgmt_config = config[:context_management]
203
- context_management do
204
- ctx_mgmt_config.each do |event_name, handler_cfg|
205
- # Capture handler_cfg in closure for each threshold
206
- captured_config = handler_cfg
207
- on(event_name.to_sym) do |ctx|
208
- action = captured_config[:action]&.to_s
209
-
210
- case action
211
- when "compress_tool_results"
212
- ctx.compress_tool_results(
213
- keep_recent: captured_config[:keep_recent] || 10,
214
- truncate_to: captured_config[:truncate_to] || 200,
215
- )
216
- when "prune_old_messages"
217
- ctx.prune_old_messages(keep_recent: captured_config[:keep_recent] || 20)
218
- when "log_warning"
219
- ctx.log_action("threshold_warning", threshold: ctx.threshold)
220
- else
221
- raise ConfigurationError, "Unknown context_management action: #{action}"
222
- end
223
- end
224
- end
225
- end
226
- end
227
-
228
- # Let plugins handle their YAML config translation
229
- # This removes SDK knowledge of plugin-specific configuration
230
- PluginRegistry.all.each do |plugin|
231
- plugin.translate_yaml_config(self, config)
232
- end
233
-
234
- self.permissions_hash = config[:permissions] if config[:permissions]
235
- end
236
- end
237
-
238
- def translate_nodes(builder)
239
- @parser.nodes.each do |node_name, node_config|
240
- builder.node(node_name) do
241
- node_config[:agents]&.each do |agent_config|
242
- agent_name = agent_config[:agent].to_sym
243
- delegates = agent_config[:delegates_to] || []
244
- reset_ctx = agent_config.key?(:reset_context) ? agent_config[:reset_context] : true
245
- tools_override = agent_config[:tools]
246
-
247
- agent_cfg = agent(agent_name, reset_context: reset_ctx)
248
-
249
- # Handle both array and hash formats for delegates_to
250
- if delegates.any?
251
- if delegates.is_a?(Hash)
252
- agent_cfg = agent_cfg.delegates_to(delegates)
253
- elsif delegates.is_a?(Array)
254
- agent_cfg = agent_cfg.delegates_to(*delegates)
255
- end
256
- end
257
-
258
- agent_cfg.tools(*tools_override) if tools_override
259
- end
260
-
261
- depends_on(*node_config[:dependencies]) if node_config[:dependencies]&.any?
262
-
263
- lead(node_config[:lead].to_sym) if node_config[:lead]
264
-
265
- if node_config[:input_command]
266
- input_command(node_config[:input_command], timeout: node_config[:input_timeout] || 60)
267
- end
268
-
269
- if node_config[:output_command]
270
- output_command(node_config[:output_command], timeout: node_config[:output_timeout] || 60)
271
- end
272
- end
273
- end
274
- end
275
-
276
- def resolve_agent_file_path(file_path)
277
- return file_path if Pathname.new(file_path).absolute?
278
-
279
- @parser.base_dir.join(file_path).to_s
280
- end
281
- end
282
- end
283
- end