swarm_sdk 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 100b5eeda25839a9c9a02270edf2d84b4623e267a55f215fa79c7479a0333f96
4
- data.tar.gz: f63cf4bf9726f769edad0b620b8738223879d2e8ceb4f2111b9be317868ace46
3
+ metadata.gz: ce2f1c2ddb88cfb9a4fe7505fc0417494c2baf22a41678cbe7eb9085d96f136f
4
+ data.tar.gz: 5d5c8174d6f96d52f6855636d5fc7c485db6a0470e11d18ee2b5866fcd91431a
5
5
  SHA512:
6
- metadata.gz: 742e655084a0c9307694ef1d10b868cee0f3b3af77f7c5b3bd0bd6bf25f3a97a677bb31e6dda8a9d9074f072099e575fbc8ba7bb12e6e9b56431fc839436e30b
7
- data.tar.gz: 8d943e6f17b77eac4d747ba9b9a73fdc1eb2600a40646b641ae226b6e239acb7daab3271b345a8b712cf0d405170f2251f5a7b29eae77f4d991c56f9af8ce828
6
+ metadata.gz: 5e503eeda171b1840cabc10fccc5f98d1c4bcd75eba1782f885fd19cc09219b39850662c901793ff9e9d0cbfbdfcc9759071c782476bb793ab87f9ddd94c3ae9
7
+ data.tar.gz: 054e3a4db5ead9c96e2d332a1a85e9102bdaabe8e50c1e3163776789709289b5fbc73fce5f73b4af856c6e1ca0a3e6f5b3cbf4918f10f6a946c9ef6223384b12
@@ -250,8 +250,39 @@ module SwarmSDK
250
250
  end
251
251
 
252
252
  # Set delegation targets
253
- def delegates_to(*agent_names)
254
- @delegates_to.concat(agent_names)
253
+ #
254
+ # Supports multiple formats for flexibility:
255
+ #
256
+ # @example Simple array (backwards compatible)
257
+ # delegates_to :frontend, :backend, :qa
258
+ #
259
+ # @example Hash with custom tool names
260
+ # delegates_to frontend: "AskFrontend",
261
+ # backend: "GetBackendHelp",
262
+ # qa: "RequestReview"
263
+ #
264
+ # @example Mixed - some auto, some custom
265
+ # delegates_to :frontend,
266
+ # backend: "GetBackendHelp",
267
+ # :qa
268
+ #
269
+ # @param agent_names_and_options [Array<Symbol, Hash>] Agent names and/or hash with custom tool names
270
+ # @return [void]
271
+ def delegates_to(*agent_names_and_options)
272
+ agent_names_and_options.each do |item|
273
+ case item
274
+ when Symbol, String
275
+ # Simple format: :frontend
276
+ @delegates_to << { agent: item.to_sym, tool_name: nil }
277
+ when Hash
278
+ # Hash format: { frontend: "AskFrontend", backend: nil }
279
+ item.each do |agent, tool_name|
280
+ @delegates_to << { agent: agent.to_sym, tool_name: tool_name }
281
+ end
282
+ else
283
+ raise ConfigurationError, "delegates_to accepts Symbols or Hashes, got #{item.class}"
284
+ end
285
+ end
255
286
  end
256
287
 
257
288
  # Add a hook (Ruby block OR shell command)
@@ -24,7 +24,7 @@ module SwarmSDK
24
24
  :context_window,
25
25
  :directory,
26
26
  :tools,
27
- :delegates_to,
27
+ :delegation_configs, # Full delegation config with tool names
28
28
  :system_prompt,
29
29
  :provider,
30
30
  :base_url,
@@ -125,7 +125,8 @@ module SwarmSDK
125
125
  # Inject default write restrictions for security
126
126
  @tools = inject_default_write_permissions(@tools)
127
127
 
128
- @delegates_to = Array(config[:delegates_to] || []).map(&:to_sym).uniq
128
+ # Parse delegation configuration (supports both simple arrays and custom tool names)
129
+ @delegation_configs = parse_delegation_config(config[:delegates_to])
129
130
  @mcp_servers = Array(config[:mcp_servers] || [])
130
131
 
131
132
  # Parse hooks configuration
@@ -135,6 +136,20 @@ module SwarmSDK
135
136
  validate!
136
137
  end
137
138
 
139
+ # Get agent names that this agent delegates to (backwards compatible)
140
+ #
141
+ # Returns an array of agent name symbols. This maintains backwards compatibility
142
+ # with existing code that expects delegates_to to be a simple array.
143
+ #
144
+ # @return [Array<Symbol>] Delegate agent names
145
+ #
146
+ # @example
147
+ # agent_definition.delegates_to
148
+ # # => [:frontend, :backend, :qa]
149
+ def delegates_to
150
+ @delegation_configs.map { |config| config[:agent] }
151
+ end
152
+
138
153
  # Get plugin-specific configuration
139
154
  #
140
155
  # Plugins store their configuration in the generic plugin_configs hash.
@@ -146,7 +161,7 @@ module SwarmSDK
146
161
  #
147
162
  # @example
148
163
  # agent_definition.plugin_config(:memory)
149
- # # => { directory: "tmp/memory", mode: :researcher }
164
+ # # => { directory: "tmp/memory", mode: :full_access }
150
165
  def plugin_config(plugin_name)
151
166
  @plugin_configs[plugin_name.to_sym] || @plugin_configs[plugin_name.to_s]
152
167
  end
@@ -160,7 +175,7 @@ module SwarmSDK
160
175
  context_window: @context_window,
161
176
  directory: @directory,
162
177
  tools: @tools,
163
- delegates_to: @delegates_to,
178
+ delegates_to: @delegation_configs, # Serialize full config
164
179
  system_prompt: @system_prompt,
165
180
  provider: @provider,
166
181
  base_url: @base_url,
@@ -285,6 +300,51 @@ module SwarmSDK
285
300
  File.expand_path(directory_config.to_s)
286
301
  end
287
302
 
303
+ # Parse delegation configuration
304
+ #
305
+ # Supports multiple formats for backwards compatibility and new features:
306
+ # 1. Simple array (backwards compatible): [:frontend, :backend]
307
+ # 2. Hash with custom tool names: { frontend: "AskFrontend", backend: nil }
308
+ # 3. Array of hashes: [{ agent: :frontend, tool_name: "AskFrontend" }]
309
+ #
310
+ # Returns normalized format: [{agent: :name, tool_name: "Custom" or nil}]
311
+ #
312
+ # @param delegation_config [nil, Array, Hash] Delegation configuration
313
+ # @return [Array<Hash>] Normalized delegation config
314
+ def parse_delegation_config(delegation_config)
315
+ return [] if delegation_config.nil?
316
+ return [] if delegation_config.respond_to?(:empty?) && delegation_config.empty?
317
+
318
+ # Handle array format (could be symbols or hashes)
319
+ if delegation_config.is_a?(Array)
320
+ delegation_config.flat_map do |item|
321
+ case item
322
+ when Symbol, String
323
+ # Simple format: :frontend → {agent: :frontend, tool_name: nil}
324
+ [{ agent: item.to_sym, tool_name: nil }]
325
+ when Hash
326
+ # Could be already normalized or hash format
327
+ if item.key?(:agent)
328
+ # Already normalized: {agent: :frontend, tool_name: "Custom"}
329
+ [item]
330
+ else
331
+ # Hash format in array: {frontend: "AskFrontend"}
332
+ item.map { |agent, tool_name| { agent: agent.to_sym, tool_name: tool_name } }
333
+ end
334
+ else
335
+ raise ConfigurationError, "Invalid delegation config format: #{item.inspect}"
336
+ end
337
+ end.uniq { |config| config[:agent] } # Remove duplicates by agent name
338
+ elsif delegation_config.is_a?(Hash)
339
+ # Hash format: {frontend: "AskFrontend", backend: nil}
340
+ delegation_config.map do |agent, tool_name|
341
+ { agent: agent.to_sym, tool_name: tool_name }
342
+ end
343
+ else
344
+ raise ConfigurationError, "delegates_to must be an Array or Hash, got #{delegation_config.class}"
345
+ end
346
+ end
347
+
288
348
  # Extract plugin-specific configuration keys from the config hash
289
349
  #
290
350
  # Standard SDK keys are filtered out, leaving only plugin-specific keys.
@@ -276,8 +276,17 @@ module SwarmSDK
276
276
  builder.tools(*tool_names)
277
277
  end
278
278
 
279
- # Add delegates_to
280
- builder.delegates_to(*config[:delegates_to]) if config[:delegates_to]&.any?
279
+ # Add delegates_to (handle both array and hash formats)
280
+ if config[:delegates_to]&.any?
281
+ delegation_config = config[:delegates_to]
282
+ if delegation_config.is_a?(Hash)
283
+ # Hash format: pass as single argument
284
+ builder.delegates_to(delegation_config)
285
+ elsif delegation_config.is_a?(Array)
286
+ # Array format: splat the array
287
+ builder.delegates_to(*delegation_config)
288
+ end
289
+ end
281
290
 
282
291
  # Add MCP servers
283
292
  config[:mcp_servers]&.each do |server|
@@ -333,7 +342,15 @@ module SwarmSDK
333
342
  when :tools
334
343
  merged[:tools] = Array(merged[:tools]) + Array(value)
335
344
  when :delegates_to
336
- merged[:delegates_to] = Array(merged[:delegates_to]) + Array(value)
345
+ # Handle merging delegation configs (can be array or hash)
346
+ existing = merged[:delegates_to] || []
347
+ new_value = value || []
348
+
349
+ # Convert both to array of delegation configs for merging
350
+ existing_array = normalize_delegation_array(existing)
351
+ new_array = normalize_delegation_array(new_value)
352
+
353
+ merged[:delegates_to] = existing_array + new_array
337
354
  when :parameters
338
355
  merged[:parameters] = (merged[:parameters] || {}).merge(value || {})
339
356
  when :headers
@@ -357,6 +374,35 @@ module SwarmSDK
357
374
  merged
358
375
  end
359
376
 
377
+ # Normalize delegation config to array of hashes format
378
+ #
379
+ # Converts various delegation formats to normalized array for merging:
380
+ # - Array of symbols: [:frontend, :backend] → [{agent: :frontend, tool_name: nil}, ...]
381
+ # - Hash: {frontend: "Custom"} → [{agent: :frontend, tool_name: "Custom"}, ...]
382
+ # - Array of hashes: [{agent: :frontend, tool_name: "Custom"}] → unchanged
383
+ #
384
+ # @param delegation_config [Array, Hash] Delegation configuration
385
+ # @return [Array<Hash>] Normalized array of {agent:, tool_name:} hashes
386
+ def normalize_delegation_array(delegation_config)
387
+ return [] if delegation_config.nil? || (delegation_config.respond_to?(:empty?) && delegation_config.empty?)
388
+
389
+ case delegation_config
390
+ when Array
391
+ delegation_config.map do |item|
392
+ case item
393
+ when Symbol, String
394
+ { agent: item.to_sym, tool_name: nil }
395
+ when Hash
396
+ item.key?(:agent) ? item : item.map { |agent, tool_name| { agent: agent.to_sym, tool_name: tool_name } }
397
+ end
398
+ end.flatten
399
+ when Hash
400
+ delegation_config.map { |agent, tool_name| { agent: agent.to_sym, tool_name: tool_name } }
401
+ else
402
+ []
403
+ end
404
+ end
405
+
360
406
  # Apply all_agents defaults to an agent builder
361
407
  #
362
408
  # @param agent_builder [Agent::Builder] The agent builder to configure
@@ -90,7 +90,28 @@ module SwarmSDK
90
90
  return [] unless agent_config
91
91
 
92
92
  delegates = agent_config[:delegates_to] || []
93
- Array(delegates).map(&:to_sym)
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
94
115
  end
95
116
 
96
117
  attr_reader :base_dir
@@ -168,7 +168,19 @@ module SwarmSDK
168
168
  tools(*tool_names)
169
169
  end
170
170
 
171
- delegates_to(*config[:delegates_to]) if config[:delegates_to]&.any?
171
+ # Handle both array and hash formats for delegates_to
172
+ if config[:delegates_to]&.any?
173
+ delegation_config = config[:delegates_to]
174
+ if delegation_config.is_a?(Hash)
175
+ # Hash format: { frontend: "Custom", backend: nil }
176
+ # Pass as single hash argument, not splatted
177
+ delegates_to(delegation_config)
178
+ elsif delegation_config.is_a?(Array)
179
+ # Array format: [:frontend, :backend] OR [{agent: :frontend, tool_name: "Custom"}]
180
+ # Splat the array
181
+ delegates_to(*delegation_config)
182
+ end
183
+ end
172
184
 
173
185
  config[:mcp_servers]&.each do |server|
174
186
  mcp_server(server[:name], **server.except(:name))
@@ -229,7 +241,16 @@ module SwarmSDK
229
241
  tools_override = agent_config[:tools]
230
242
 
231
243
  agent_cfg = agent(agent_name, reset_context: reset_ctx)
232
- agent_cfg = agent_cfg.delegates_to(*delegates) if delegates.any?
244
+
245
+ # Handle both array and hash formats for delegates_to
246
+ if delegates.any?
247
+ if delegates.is_a?(Hash)
248
+ agent_cfg = agent_cfg.delegates_to(delegates)
249
+ elsif delegates.is_a?(Array)
250
+ agent_cfg = agent_cfg.delegates_to(*delegates)
251
+ end
252
+ end
253
+
233
254
  agent_cfg.tools(*tools_override) if tools_override
234
255
  end
235
256
 
@@ -104,7 +104,28 @@ module SwarmSDK
104
104
  return [] unless agent_config
105
105
 
106
106
  delegates = agent_config[:delegates_to] || []
107
- Array(delegates).map(&:to_sym)
107
+
108
+ # Handle both array and hash formats for delegates_to
109
+ case delegates
110
+ when Array
111
+ # Array of symbols: [:frontend, :backend]
112
+ # OR array of hashes: [{agent: :frontend, tool_name: "Custom"}]
113
+ delegates.map do |item|
114
+ case item
115
+ when Symbol, String
116
+ item.to_sym
117
+ when Hash
118
+ # Extract agent name from hash format
119
+ agent_name = item[:agent] || item["agent"]
120
+ agent_name&.to_sym
121
+ end
122
+ end.compact # Remove nils from malformed hashes
123
+ when Hash
124
+ # Hash format: {frontend: "Custom", backend: nil}
125
+ delegates.keys.map(&:to_sym)
126
+ else
127
+ []
128
+ end
108
129
  end
109
130
 
110
131
  # Convert configuration to Swarm or Workflow using appropriate builder
@@ -88,8 +88,9 @@ module SwarmSDK
88
88
  # @param delegate_chat [Agent::Chat] The delegate's chat instance
89
89
  # @param agent_name [Symbol] Name of the delegating agent
90
90
  # @param delegating_chat [Agent::Chat, nil] The chat instance of the agent doing the delegating
91
+ # @param custom_tool_name [String, nil] Optional custom tool name (overrides auto-generated name)
91
92
  # @return [Tools::Delegate] Delegation tool
92
- def create_delegation_tool(name:, description:, delegate_chat:, agent_name:, delegating_chat: nil)
93
+ def create_delegation_tool(name:, description:, delegate_chat:, agent_name:, delegating_chat: nil, custom_tool_name: nil)
93
94
  Tools::Delegate.new(
94
95
  delegate_name: name,
95
96
  delegate_description: description,
@@ -97,6 +98,7 @@ module SwarmSDK
97
98
  agent_name: agent_name,
98
99
  swarm: @swarm,
99
100
  delegating_chat: delegating_chat,
101
+ custom_tool_name: custom_tool_name,
100
102
  )
101
103
  end
102
104
 
@@ -140,8 +142,8 @@ module SwarmSDK
140
142
 
141
143
  # Sub-pass 2a: Create delegation instances for isolated agents
142
144
  @swarm.agent_definitions.each do |delegator_name, delegator_def|
143
- delegator_def.delegates_to.each do |delegate_base_name|
144
- delegate_base_name = delegate_base_name.to_sym
145
+ delegator_def.delegation_configs.each do |delegation_config|
146
+ delegate_base_name = delegation_config[:agent]
145
147
 
146
148
  unless @swarm.agent_definitions.key?(delegate_base_name)
147
149
  raise ConfigurationError,
@@ -177,11 +179,11 @@ module SwarmSDK
177
179
  # Skip if delegator doesn't exist as primary (wasn't created in pass_1)
178
180
  next unless delegator_chat
179
181
 
180
- delegator_def.delegates_to.each do |delegate_name|
182
+ delegator_def.delegation_configs.each do |delegation_config|
181
183
  wire_delegation(
182
184
  delegator_name: delegator_name,
183
185
  delegator_chat: delegator_chat,
184
- delegate_name: delegate_name,
186
+ delegation_config: delegation_config,
185
187
  tool_configurator: tool_configurator,
186
188
  create_nested_instances: false,
187
189
  )
@@ -195,11 +197,11 @@ module SwarmSDK
195
197
  delegate_definition = @swarm.agent_definitions[base_name]
196
198
 
197
199
  # Register delegation tools for THIS instance's delegates_to
198
- delegate_definition.delegates_to.each do |nested_delegate_name|
200
+ delegate_definition.delegation_configs.each do |delegation_config|
199
201
  wire_delegation(
200
202
  delegator_name: instance_name.to_sym,
201
203
  delegator_chat: delegation_chat,
202
- delegate_name: nested_delegate_name,
204
+ delegation_config: delegation_config,
203
205
  tool_configurator: tool_configurator,
204
206
  create_nested_instances: true,
205
207
  )
@@ -215,22 +217,24 @@ module SwarmSDK
215
217
  #
216
218
  # @param delegator_name [Symbol, String] Name of the agent doing the delegating
217
219
  # @param delegator_chat [Agent::Chat] Chat instance of the delegator
218
- # @param delegate_name [Symbol, String] Name of the delegate target
220
+ # @param delegation_config [Hash] Delegation configuration with :agent and :tool_name keys
219
221
  # @param tool_configurator [ToolConfigurator] Tool configuration helper
220
222
  # @param create_nested_instances [Boolean] Whether to create new instances for nested delegation
221
223
  # @return [void]
222
- def wire_delegation(delegator_name:, delegator_chat:, delegate_name:, tool_configurator:, create_nested_instances:)
223
- delegate_name_str = delegate_name.to_s
224
- delegate_name_sym = delegate_name.to_sym
224
+ def wire_delegation(delegator_name:, delegator_chat:, delegation_config:, tool_configurator:, create_nested_instances:)
225
+ delegate_name_sym = delegation_config[:agent]
226
+ delegate_name_str = delegate_name_sym.to_s
227
+ custom_tool_name = delegation_config[:tool_name]
225
228
 
226
229
  # Check if target is a registered swarm
227
230
  if @swarm.swarm_registry&.registered?(delegate_name_str)
228
- wire_swarm_delegation(delegator_name, delegator_chat, delegate_name_str)
231
+ wire_swarm_delegation(delegator_name, delegator_chat, delegate_name_str, custom_tool_name)
229
232
  elsif @swarm.agent_definitions.key?(delegate_name_sym)
230
233
  wire_agent_delegation(
231
234
  delegator_name: delegator_name,
232
235
  delegator_chat: delegator_chat,
233
236
  delegate_name_sym: delegate_name_sym,
237
+ custom_tool_name: custom_tool_name,
234
238
  tool_configurator: tool_configurator,
235
239
  create_nested_instances: create_nested_instances,
236
240
  )
@@ -245,14 +249,16 @@ module SwarmSDK
245
249
  # @param delegator_name [Symbol, String] Name of the delegating agent
246
250
  # @param delegator_chat [Agent::Chat] Chat instance of the delegator
247
251
  # @param swarm_name [String] Name of the registered swarm
252
+ # @param custom_tool_name [String, nil] Optional custom tool name
248
253
  # @return [void]
249
- def wire_swarm_delegation(delegator_name, delegator_chat, swarm_name)
254
+ def wire_swarm_delegation(delegator_name, delegator_chat, swarm_name, custom_tool_name)
250
255
  tool = create_delegation_tool(
251
256
  name: swarm_name,
252
257
  description: "External swarm: #{swarm_name}",
253
258
  delegate_chat: nil, # Swarm delegation - no direct chat
254
259
  agent_name: delegator_name,
255
260
  delegating_chat: delegator_chat,
261
+ custom_tool_name: custom_tool_name,
256
262
  )
257
263
 
258
264
  delegator_chat.add_tool(tool)
@@ -266,10 +272,11 @@ module SwarmSDK
266
272
  # @param delegator_name [Symbol, String] Name of the delegating agent
267
273
  # @param delegator_chat [Agent::Chat] Chat instance of the delegator
268
274
  # @param delegate_name_sym [Symbol] Name of the delegate agent
275
+ # @param custom_tool_name [String, nil] Optional custom tool name
269
276
  # @param tool_configurator [ToolConfigurator] Tool configuration helper
270
277
  # @param create_nested_instances [Boolean] Whether to create new instances if not found
271
278
  # @return [void]
272
- def wire_agent_delegation(delegator_name:, delegator_chat:, delegate_name_sym:, tool_configurator:, create_nested_instances:)
279
+ def wire_agent_delegation(delegator_name:, delegator_chat:, delegate_name_sym:, custom_tool_name:, tool_configurator:, create_nested_instances:)
273
280
  delegate_definition = @swarm.agent_definitions[delegate_name_sym]
274
281
 
275
282
  # Determine which chat instance to use
@@ -301,6 +308,7 @@ module SwarmSDK
301
308
  delegate_chat: target_chat,
302
309
  agent_name: delegator_name,
303
310
  delegating_chat: delegator_chat,
311
+ custom_tool_name: custom_tool_name,
304
312
  )
305
313
 
306
314
  delegator_chat.add_tool(tool)
@@ -326,8 +334,10 @@ module SwarmSDK
326
334
 
327
335
  # Setup context for an agent (primary or delegation instance)
328
336
  def setup_agent_context(agent_name, agent_definition, chat, is_delegation: false)
329
- delegate_tool_names = agent_definition.delegates_to.map do |delegate_name|
330
- Tools::Delegate.tool_name_for(delegate_name)
337
+ # Generate actual tool names (custom or auto-generated) for context tracking
338
+ delegate_tool_names = agent_definition.delegation_configs.map do |delegation_config|
339
+ # Use custom name if provided, otherwise auto-generate
340
+ delegation_config[:tool_name] || Tools::Delegate.tool_name_for(delegation_config[:agent])
331
341
  end
332
342
 
333
343
  context = Agent::Context.new(
@@ -36,13 +36,15 @@ module SwarmSDK
36
36
  # @param agent_name [Symbol, String] Name of the agent using this tool
37
37
  # @param swarm [Swarm] The swarm instance (provides hook_registry, delegation_call_stack, swarm_registry)
38
38
  # @param delegating_chat [Agent::Chat, nil] The chat instance of the agent doing the delegating (for accessing hooks)
39
+ # @param custom_tool_name [String, nil] Optional custom tool name (overrides auto-generated name)
39
40
  def initialize(
40
41
  delegate_name:,
41
42
  delegate_description:,
42
43
  delegate_chat:,
43
44
  agent_name:,
44
45
  swarm:,
45
- delegating_chat: nil
46
+ delegating_chat: nil,
47
+ custom_tool_name: nil
46
48
  )
47
49
  super()
48
50
 
@@ -53,8 +55,8 @@ module SwarmSDK
53
55
  @swarm = swarm
54
56
  @delegating_chat = delegating_chat
55
57
 
56
- # Generate tool name using canonical method
57
- @tool_name = self.class.tool_name_for(delegate_name)
58
+ # Use custom tool name if provided, otherwise generate using canonical method
59
+ @tool_name = custom_tool_name || self.class.tool_name_for(delegate_name)
58
60
  @delegate_target = delegate_name.to_s
59
61
  end
60
62
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- VERSION = "2.6.0"
4
+ VERSION = "2.6.1"
5
5
  end
@@ -37,10 +37,26 @@ module SwarmSDK
37
37
 
38
38
  # Set delegation targets for this agent
39
39
  #
40
- # @param agent_names [Array<Symbol>] Names of agents to delegate to
40
+ # Supports multiple formats for flexibility:
41
+ # - Array: delegates_to(:frontend, :backend)
42
+ # - Hash: delegates_to(frontend: "AskFrontend", backend: "GetBackend")
43
+ #
44
+ # @param agent_names_and_options [Array<Symbol, Hash>] Names and/or hash with custom tool names
41
45
  # @return [self] For method chaining
42
- def delegates_to(*agent_names)
43
- @delegates_to = agent_names.map(&:to_sym)
46
+ def delegates_to(*agent_names_and_options)
47
+ # Parse delegation configs (same logic as Agent::Builder)
48
+ @delegates_to = []
49
+ agent_names_and_options.each do |item|
50
+ case item
51
+ when Symbol, String
52
+ @delegates_to << { agent: item.to_sym, tool_name: nil }
53
+ when Hash
54
+ item.each do |agent, tool_name|
55
+ @delegates_to << { agent: agent.to_sym, tool_name: tool_name }
56
+ end
57
+ end
58
+ end
59
+
44
60
  update_registration
45
61
  self
46
62
  end
@@ -183,10 +183,45 @@ module SwarmSDK
183
183
  @nodes.values.flat_map do |node_builder|
184
184
  # Collect both direct agents and their delegation targets
185
185
  node_builder.agent_configs.flat_map do |config|
186
- [config[:agent]] + Array(config[:delegates_to])
186
+ # Extract delegate agent names (handles array and hash formats)
187
+ delegate_names = extract_delegate_agent_names(config[:delegates_to] || [])
188
+ [config[:agent]] + delegate_names
187
189
  end
188
190
  end.uniq
189
191
  end
192
+
193
+ # Extract agent names from delegation configuration
194
+ #
195
+ # Handles multiple formats:
196
+ # - Array of symbols: [:frontend, :backend]
197
+ # - Hash: {frontend: "Custom", backend: nil}
198
+ # - Array of hashes: [{agent: :frontend, tool_name: "Custom"}]
199
+ #
200
+ # @param delegation_config [Array, Hash, nil] Delegation configuration
201
+ # @return [Array<Symbol>] Array of agent name symbols
202
+ def extract_delegate_agent_names(delegation_config)
203
+ return [] if delegation_config.nil?
204
+ return [] if delegation_config.respond_to?(:empty?) && delegation_config.empty?
205
+
206
+ case delegation_config
207
+ when Array
208
+ delegation_config.map do |item|
209
+ case item
210
+ when Symbol, String
211
+ item.to_sym
212
+ when Hash
213
+ # Extract agent name from normalized format
214
+ agent_name = item[:agent] || item["agent"]
215
+ agent_name&.to_sym
216
+ end
217
+ end.compact # Remove nils from malformed hashes
218
+ when Hash
219
+ # Hash format: keys are agent names
220
+ delegation_config.keys.map(&:to_sym)
221
+ else
222
+ []
223
+ end
224
+ end
190
225
  end
191
226
  end
192
227
  end
@@ -541,7 +541,10 @@ module SwarmSDK
541
541
  # @return [void]
542
542
  def auto_add_delegate_agents
543
543
  # Collect all agents mentioned in delegates_to
544
- all_delegates = @agent_configs.flat_map { |ac| ac[:delegates_to] }.uniq
544
+ # Extract agent names from all delegation configs (handles hash and array formats)
545
+ all_delegates = @agent_configs.flat_map do |ac|
546
+ extract_delegate_agent_names(ac[:delegates_to] || [])
547
+ end.uniq
545
548
 
546
549
  # Find delegates that aren't explicitly declared
547
550
  declared_agents = @agent_configs.map { |ac| ac[:agent] }
@@ -552,6 +555,39 @@ module SwarmSDK
552
555
  @agent_configs << { agent: delegate_name, delegates_to: [], reset_context: true }
553
556
  end
554
557
  end
558
+
559
+ # Extract agent names from delegation configuration
560
+ #
561
+ # Handles multiple formats:
562
+ # - Array of symbols: [:frontend, :backend]
563
+ # - Hash: {frontend: "Custom", backend: nil}
564
+ # - Array of hashes: [{agent: :frontend, tool_name: "Custom"}]
565
+ #
566
+ # @param delegation_config [Array, Hash, nil] Delegation configuration
567
+ # @return [Array<Symbol>] Array of agent name symbols
568
+ def extract_delegate_agent_names(delegation_config)
569
+ return [] if delegation_config.nil?
570
+ return [] if delegation_config.respond_to?(:empty?) && delegation_config.empty?
571
+
572
+ case delegation_config
573
+ when Array
574
+ delegation_config.map do |item|
575
+ case item
576
+ when Symbol, String
577
+ item.to_sym
578
+ when Hash
579
+ # Extract agent name from normalized format
580
+ agent_name = item[:agent] || item["agent"]
581
+ agent_name&.to_sym
582
+ end
583
+ end.compact # Remove nils from malformed hashes
584
+ when Hash
585
+ # Hash format: keys are agent names
586
+ delegation_config.keys.map(&:to_sym)
587
+ else
588
+ []
589
+ end
590
+ end
555
591
  end
556
592
  end
557
593
  end
@@ -377,7 +377,9 @@ module SwarmSDK
377
377
  end
378
378
 
379
379
  # Validate delegation targets exist
380
- config[:delegates_to].each do |delegate|
380
+ # Extract agent names from delegation config (supports both array and hash formats)
381
+ delegate_names = extract_delegate_agent_names(config[:delegates_to])
382
+ delegate_names.each do |delegate|
381
383
  unless @agent_definitions.key?(delegate)
382
384
  raise ConfigurationError,
383
385
  "Node '#{node_name}' agent '#{agent_name}' delegates to undefined agent '#{delegate}'"
@@ -550,5 +552,38 @@ module SwarmSDK
550
552
  "Invalid scratchpad mode: #{value.inspect}. Use :enabled, :per_node, or :disabled"
551
553
  end
552
554
  end
555
+
556
+ # Extract agent names from delegation configuration
557
+ #
558
+ # Handles multiple formats:
559
+ # - Array of symbols: [:frontend, :backend]
560
+ # - Hash: {frontend: "Custom", backend: nil}
561
+ # - Array of hashes: [{agent: :frontend, tool_name: "Custom"}]
562
+ #
563
+ # @param delegation_config [Array, Hash, nil] Delegation configuration
564
+ # @return [Array<Symbol>] Array of agent name symbols
565
+ def extract_delegate_agent_names(delegation_config)
566
+ return [] if delegation_config.nil?
567
+ return [] if delegation_config.respond_to?(:empty?) && delegation_config.empty?
568
+
569
+ case delegation_config
570
+ when Array
571
+ delegation_config.map do |item|
572
+ case item
573
+ when Symbol, String
574
+ item.to_sym
575
+ when Hash
576
+ # Extract agent name from normalized format
577
+ agent_name = item[:agent] || item["agent"]
578
+ agent_name&.to_sym
579
+ end
580
+ end.compact # Remove nils from malformed hashes
581
+ when Hash
582
+ # Hash format: keys are agent names
583
+ delegation_config.keys.map(&:to_sym)
584
+ else
585
+ []
586
+ end
587
+ end
553
588
  end
554
589
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swarm_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.0
4
+ version: 2.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda