swarm_sdk 2.7.14 → 3.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +16 -0
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +4 -1
  4. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  5. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  6. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  7. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  8. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  9. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  10. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  11. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  12. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  13. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  14. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  15. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  16. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  17. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  18. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  19. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  20. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  24. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  25. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  26. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  27. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  28. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  29. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  30. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  31. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  32. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  33. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  34. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  35. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  36. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  37. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  38. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  39. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  40. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  41. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  42. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  43. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  44. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  45. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  46. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  47. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  48. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  49. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  50. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  51. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  52. data/lib/swarm_sdk/v3.rb +145 -0
  53. metadata +83 -148
  54. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  55. data/lib/swarm_sdk/agent/builder.rb +0 -705
  56. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  57. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  58. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  59. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  60. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  61. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  62. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  63. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  64. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  65. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  66. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  67. data/lib/swarm_sdk/agent/context.rb +0 -115
  68. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  69. data/lib/swarm_sdk/agent/definition.rb +0 -588
  70. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  71. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  72. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  73. data/lib/swarm_sdk/agent_registry.rb +0 -146
  74. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  75. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  76. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  77. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  78. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  79. data/lib/swarm_sdk/config.rb +0 -368
  80. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  81. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  82. data/lib/swarm_sdk/configuration.rb +0 -165
  83. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  84. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  85. data/lib/swarm_sdk/context_compactor.rb +0 -335
  86. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  87. data/lib/swarm_sdk/context_management/context.rb +0 -328
  88. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  89. data/lib/swarm_sdk/defaults.rb +0 -251
  90. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  91. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  92. data/lib/swarm_sdk/hooks/context.rb +0 -197
  93. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  94. data/lib/swarm_sdk/hooks/error.rb +0 -29
  95. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  96. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  97. data/lib/swarm_sdk/hooks/result.rb +0 -150
  98. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  99. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  100. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  101. data/lib/swarm_sdk/log_collector.rb +0 -227
  102. data/lib/swarm_sdk/log_stream.rb +0 -127
  103. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  104. data/lib/swarm_sdk/model_aliases.json +0 -8
  105. data/lib/swarm_sdk/models.json +0 -44002
  106. data/lib/swarm_sdk/models.rb +0 -161
  107. data/lib/swarm_sdk/node_context.rb +0 -245
  108. data/lib/swarm_sdk/observer/builder.rb +0 -81
  109. data/lib/swarm_sdk/observer/config.rb +0 -45
  110. data/lib/swarm_sdk/observer/manager.rb +0 -248
  111. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  112. data/lib/swarm_sdk/permissions/config.rb +0 -239
  113. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  114. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  115. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  116. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  117. data/lib/swarm_sdk/plugin.rb +0 -309
  118. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  119. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  120. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  121. data/lib/swarm_sdk/restore_result.rb +0 -65
  122. data/lib/swarm_sdk/result.rb +0 -241
  123. data/lib/swarm_sdk/snapshot.rb +0 -156
  124. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  125. data/lib/swarm_sdk/state_restorer.rb +0 -476
  126. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  127. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  128. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  129. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  130. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  131. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  132. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  133. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  134. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  135. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  136. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  137. data/lib/swarm_sdk/swarm.rb +0 -973
  138. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  139. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  140. data/lib/swarm_sdk/tools/base.rb +0 -63
  141. data/lib/swarm_sdk/tools/bash.rb +0 -280
  142. data/lib/swarm_sdk/tools/clock.rb +0 -46
  143. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  144. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  145. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  146. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  147. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  148. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  149. data/lib/swarm_sdk/tools/edit.rb +0 -145
  150. data/lib/swarm_sdk/tools/glob.rb +0 -166
  151. data/lib/swarm_sdk/tools/grep.rb +0 -235
  152. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  153. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  154. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  155. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  156. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  157. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  158. data/lib/swarm_sdk/tools/read.rb +0 -261
  159. data/lib/swarm_sdk/tools/registry.rb +0 -205
  160. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  161. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  163. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  164. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  165. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  166. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  167. data/lib/swarm_sdk/tools/think.rb +0 -100
  168. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  169. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  170. data/lib/swarm_sdk/tools/write.rb +0 -112
  171. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  172. data/lib/swarm_sdk/utils.rb +0 -68
  173. data/lib/swarm_sdk/validation_result.rb +0 -33
  174. data/lib/swarm_sdk/version.rb +0 -5
  175. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  176. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  177. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  178. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  179. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  180. data/lib/swarm_sdk/workflow.rb +0 -589
  181. data/lib/swarm_sdk.rb +0 -721
@@ -1,361 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- class Swarm
5
- # Logging callbacks for swarm events
6
- #
7
- # Extracted from Swarm to reduce class size and eliminate repetitive callback patterns.
8
- # These callbacks emit structured log events to LogStream for monitoring and debugging.
9
- module LoggingCallbacks
10
- # Register default logging callbacks for all swarm events
11
- #
12
- # Sets up low-priority callbacks that emit structured events to LogStream.
13
- # These callbacks only fire when LogStream.emitter is set (logging enabled).
14
- def register_default_logging_callbacks
15
- register_swarm_lifecycle_callbacks
16
- register_agent_lifecycle_callbacks
17
- register_tool_execution_callbacks
18
- register_context_warning_callback
19
- end
20
-
21
- # Setup logging infrastructure for an execution
22
- #
23
- # @param logs [Array] Log collection array
24
- # @yield [entry] Block called for each log entry
25
- def setup_logging(logs)
26
- # Force fresh subscription array for this execution
27
- Fiber[:log_subscriptions] = []
28
-
29
- # Subscribe to collect logs and forward to user's block
30
- LogCollector.subscribe do |entry|
31
- logs << entry
32
- yield(entry) if block_given?
33
- end
34
-
35
- # Set LogStream to use LogCollector as emitter
36
- LogStream.emitter = LogCollector
37
- end
38
-
39
- # Emit agent_start events if agents were initialized before logging was set up
40
- #
41
- # When agents are initialized BEFORE logging (e.g., via restore()),
42
- # we need to retroactively set up logging callbacks and emit agent_start events.
43
- def emit_retroactive_agent_start_events
44
- return if !@agents_initialized || @agent_start_events_emitted
45
-
46
- # Setup logging callbacks for all agents (they were skipped during initialization)
47
- setup_logging_for_all_agents
48
-
49
- # Emit agent_start events now that logging is ready
50
- emit_agent_start_events
51
- @agent_start_events_emitted = true
52
- end
53
-
54
- # Setup logging callbacks for all initialized agents
55
- #
56
- # Called after restore() when logging is enabled. Sets up logging callbacks
57
- # for each agent so that subsequent events are captured.
58
- def setup_logging_for_all_agents
59
- # Setup for PRIMARY agents
60
- @agents.each_value do |chat|
61
- chat.setup_logging if chat.respond_to?(:setup_logging)
62
- end
63
-
64
- # Setup for DELEGATION instances
65
- @delegation_instances.each_value do |chat|
66
- chat.setup_logging if chat.respond_to?(:setup_logging)
67
- end
68
- end
69
-
70
- # Emit agent_start events for all initialized agents
71
- #
72
- # Called retroactively when agents were initialized before logging was enabled.
73
- # Emits agent_start events so log stream captures complete agent lifecycle.
74
- def emit_agent_start_events
75
- return unless LogStream.emitter
76
-
77
- # Emit for PRIMARY agents
78
- @agents.each do |agent_name, chat|
79
- emit_agent_start_for(agent_name, chat, is_delegation: false)
80
- end
81
-
82
- # Emit for DELEGATION instances
83
- @delegation_instances.each do |instance_name, chat|
84
- base_name = extract_base_name(instance_name)
85
- emit_agent_start_for(instance_name.to_sym, chat, is_delegation: true, base_name: base_name)
86
- end
87
-
88
- # Mark as emitted to prevent duplicate emissions
89
- @agent_start_events_emitted = true
90
- end
91
-
92
- # Emit a single agent_start event
93
- #
94
- # @param agent_name [Symbol] Agent name (or instance name for delegations)
95
- # @param chat [Agent::Chat] Agent chat instance
96
- # @param is_delegation [Boolean] Whether this is a delegation instance
97
- # @param base_name [String, nil] Base agent name for delegations
98
- def emit_agent_start_for(agent_name, chat, is_delegation:, base_name: nil)
99
- base_name ||= agent_name
100
- agent_def = @agent_definitions[base_name]
101
-
102
- # Build plugin storage info using base name
103
- plugin_storage_info = {}
104
- @plugin_storages.each do |plugin_name, agent_storages|
105
- next unless agent_storages.key?(base_name)
106
-
107
- plugin_storage_info[plugin_name] = {
108
- enabled: true,
109
- config: agent_def.respond_to?(plugin_name) ? extract_plugin_config_info(agent_def.public_send(plugin_name)) : nil,
110
- }
111
- end
112
-
113
- LogStream.emit(
114
- type: "agent_start",
115
- agent: agent_name,
116
- swarm_id: @swarm_id,
117
- parent_swarm_id: @parent_swarm_id,
118
- swarm_name: @name,
119
- model: agent_def.model,
120
- provider: agent_def.provider || "openai",
121
- directory: agent_def.directory,
122
- system_prompt: agent_def.system_prompt,
123
- tools: chat.tool_names,
124
- delegates_to: agent_def.delegates_to,
125
- plugin_storages: plugin_storage_info,
126
- is_delegation_instance: is_delegation,
127
- base_agent: (base_name if is_delegation),
128
- timestamp: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
129
- )
130
- end
131
-
132
- private
133
-
134
- # Register swarm lifecycle callbacks (swarm_start, swarm_stop)
135
- def register_swarm_lifecycle_callbacks
136
- add_default_callback(:swarm_start, priority: -100) do |context|
137
- emit_swarm_start_event(context)
138
- end
139
-
140
- add_default_callback(:swarm_stop, priority: -100) do |context|
141
- emit_swarm_stop_event(context)
142
- end
143
- end
144
-
145
- # Register agent lifecycle callbacks (user_prompt, agent_step, agent_stop)
146
- def register_agent_lifecycle_callbacks
147
- add_default_callback(:user_prompt, priority: -100) do |context|
148
- emit_user_prompt_event(context)
149
- end
150
-
151
- add_default_callback(:agent_step, priority: -100) do |context|
152
- emit_agent_step_event(context)
153
- end
154
-
155
- add_default_callback(:agent_stop, priority: -100) do |context|
156
- emit_agent_stop_event(context)
157
- end
158
- end
159
-
160
- # Register tool execution callbacks (pre_tool_use, post_tool_use)
161
- def register_tool_execution_callbacks
162
- add_default_callback(:pre_tool_use, priority: -100) do |context|
163
- emit_tool_call_event(context)
164
- end
165
-
166
- add_default_callback(:post_tool_use, priority: -100) do |context|
167
- emit_tool_result_event(context)
168
- end
169
- end
170
-
171
- # Register context warning callback
172
- def register_context_warning_callback
173
- add_default_callback(:context_warning, priority: -100) do |context|
174
- emit_context_warning_event(context)
175
- end
176
- end
177
-
178
- # Emit swarm_start event
179
- def emit_swarm_start_event(context)
180
- return unless LogStream.emitter
181
-
182
- LogStream.emit(
183
- type: "swarm_start",
184
- agent: context.metadata[:lead_agent],
185
- swarm_id: @swarm_id,
186
- parent_swarm_id: @parent_swarm_id,
187
- swarm_name: context.metadata[:swarm_name],
188
- lead_agent: context.metadata[:lead_agent],
189
- prompt: context.metadata[:prompt],
190
- timestamp: context.metadata[:timestamp],
191
- )
192
- end
193
-
194
- # Emit swarm_stop event
195
- def emit_swarm_stop_event(context)
196
- return unless LogStream.emitter
197
-
198
- LogStream.emit(
199
- type: "swarm_stop",
200
- swarm_id: @swarm_id,
201
- parent_swarm_id: @parent_swarm_id,
202
- swarm_name: context.metadata[:swarm_name],
203
- lead_agent: context.metadata[:lead_agent],
204
- last_agent: context.metadata[:last_agent],
205
- content: context.metadata[:content],
206
- success: context.metadata[:success],
207
- finish_reason: context.metadata[:finish_reason] || "finished",
208
- duration: context.metadata[:duration],
209
- total_cost: context.metadata[:total_cost],
210
- total_tokens: context.metadata[:total_tokens],
211
- agents_involved: context.metadata[:agents_involved],
212
- per_agent_usage: context.metadata[:per_agent_usage],
213
- timestamp: context.metadata[:timestamp],
214
- )
215
- end
216
-
217
- # Emit user_prompt event
218
- def emit_user_prompt_event(context)
219
- return unless LogStream.emitter
220
-
221
- LogStream.emit(
222
- type: "user_prompt",
223
- agent: context.agent_name,
224
- swarm_id: @swarm_id,
225
- parent_swarm_id: @parent_swarm_id,
226
- prompt: context.metadata[:prompt],
227
- model: context.metadata[:model] || "unknown",
228
- provider: context.metadata[:provider] || "unknown",
229
- message_count: context.metadata[:message_count] || 0,
230
- tools: context.metadata[:tools] || [],
231
- delegates_to: context.metadata[:delegates_to] || [],
232
- source: context.metadata[:source] || "user",
233
- metadata: context.metadata,
234
- )
235
- end
236
-
237
- # Emit agent_step event (intermediate response with tool calls)
238
- def emit_agent_step_event(context)
239
- return unless LogStream.emitter
240
-
241
- metadata_without_duplicates = context.metadata.except(
242
- :model,
243
- :content,
244
- :tool_calls,
245
- :finish_reason,
246
- :usage,
247
- :tool_executions,
248
- :citations,
249
- :search_results,
250
- )
251
-
252
- LogStream.emit(
253
- type: "agent_step",
254
- agent: context.agent_name,
255
- swarm_id: @swarm_id,
256
- parent_swarm_id: @parent_swarm_id,
257
- model: context.metadata[:model],
258
- content: context.metadata[:content],
259
- tool_calls: context.metadata[:tool_calls],
260
- finish_reason: context.metadata[:finish_reason],
261
- usage: context.metadata[:usage],
262
- citations: context.metadata[:citations],
263
- search_results: context.metadata[:search_results],
264
- tool_executions: context.metadata[:tool_executions],
265
- metadata: metadata_without_duplicates,
266
- )
267
- end
268
-
269
- # Emit agent_stop event (final response)
270
- def emit_agent_stop_event(context)
271
- return unless LogStream.emitter
272
-
273
- metadata_without_duplicates = context.metadata.except(
274
- :model,
275
- :content,
276
- :tool_calls,
277
- :finish_reason,
278
- :usage,
279
- :tool_executions,
280
- :citations,
281
- :search_results,
282
- )
283
-
284
- LogStream.emit(
285
- type: "agent_stop",
286
- agent: context.agent_name,
287
- swarm_id: @swarm_id,
288
- parent_swarm_id: @parent_swarm_id,
289
- model: context.metadata[:model],
290
- content: context.metadata[:content],
291
- tool_calls: context.metadata[:tool_calls],
292
- finish_reason: context.metadata[:finish_reason],
293
- usage: context.metadata[:usage],
294
- citations: context.metadata[:citations],
295
- search_results: context.metadata[:search_results],
296
- tool_executions: context.metadata[:tool_executions],
297
- metadata: metadata_without_duplicates,
298
- )
299
- end
300
-
301
- # Emit tool_call event (pre_tool_use)
302
- def emit_tool_call_event(context)
303
- return unless LogStream.emitter
304
-
305
- LogStream.emit(
306
- type: "tool_call",
307
- agent: context.agent_name,
308
- swarm_id: @swarm_id,
309
- parent_swarm_id: @parent_swarm_id,
310
- tool_call_id: context.tool_call.id,
311
- tool: context.tool_call.name,
312
- arguments: context.tool_call.parameters,
313
- metadata: context.metadata,
314
- )
315
- end
316
-
317
- # Emit tool_result event (post_tool_use)
318
- def emit_tool_result_event(context)
319
- return unless LogStream.emitter
320
-
321
- LogStream.emit(
322
- type: "tool_result",
323
- agent: context.agent_name,
324
- swarm_id: @swarm_id,
325
- parent_swarm_id: @parent_swarm_id,
326
- tool_call_id: context.tool_result.tool_call_id,
327
- tool: context.tool_result.tool_name,
328
- result: context.tool_result.content,
329
- metadata: context.metadata,
330
- )
331
- end
332
-
333
- # Emit context_limit_warning event
334
- def emit_context_warning_event(context)
335
- return unless LogStream.emitter
336
-
337
- LogStream.emit(
338
- type: "context_limit_warning",
339
- agent: context.agent_name,
340
- swarm_id: @swarm_id,
341
- parent_swarm_id: @parent_swarm_id,
342
- model: context.metadata[:model] || "unknown",
343
- threshold: "#{context.metadata[:threshold]}%",
344
- current_usage: "#{context.metadata[:percentage]}%",
345
- tokens_used: context.metadata[:tokens_used],
346
- tokens_remaining: context.metadata[:tokens_remaining],
347
- context_limit: context.metadata[:context_limit],
348
- metadata: context.metadata,
349
- )
350
- end
351
-
352
- # Extract base name from delegation instance name
353
- #
354
- # @param instance_name [String, Symbol] Instance name (e.g., "agent@1234")
355
- # @return [Symbol] Base agent name (e.g., :agent)
356
- def extract_base_name(instance_name)
357
- instance_name.to_s.split("@").first.to_sym
358
- end
359
- end
360
- end
361
- end
@@ -1,290 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- class Swarm
5
- # Handles MCP (Model Context Protocol) server configuration and client management
6
- #
7
- # Responsibilities:
8
- # - Register MCP servers for agents
9
- # - Initialize MCP clients (stdio, SSE, streamable transports)
10
- # - Build transport-specific configurations
11
- # - Track clients for cleanup
12
- #
13
- # This encapsulates all MCP-related logic that was previously in Swarm.
14
- class McpConfigurator
15
- def initialize(swarm)
16
- @swarm = swarm
17
- @mcp_clients = swarm.mcp_clients
18
- end
19
-
20
- # Register MCP servers for an agent
21
- #
22
- # Connects to MCP servers and registers their tools with the agent's chat instance.
23
- # Supports stdio, SSE, and HTTP (streamable) transports.
24
- #
25
- # ## Boot Optimization (Plan 025)
26
- #
27
- # - If tools specified: Create stubs without tools/list RPC (fast boot, lazy schema)
28
- # - If tools omitted: Call tools/list to discover all tools (discovery mode)
29
- #
30
- # @param chat [AgentChat] The agent's chat instance
31
- # @param mcp_server_configs [Array<Hash>] MCP server configurations
32
- # @param agent_name [Symbol] Agent name for tracking clients
33
- #
34
- # @example Fast boot mode
35
- # mcp_server :codebase, type: :stdio, command: "mcp-server", tools: [:search, :list]
36
- # # Creates tool stubs instantly, no tools/list RPC
37
- #
38
- # @example Discovery mode
39
- # mcp_server :codebase, type: :stdio, command: "mcp-server"
40
- # # Calls tools/list to discover all available tools
41
- def register_mcp_servers(chat, mcp_server_configs, agent_name:)
42
- return if mcp_server_configs.nil? || mcp_server_configs.empty?
43
-
44
- # Ensure MCP logging is configured before creating clients
45
- Swarm.apply_mcp_logging_configuration
46
-
47
- mcp_server_configs.each do |server_config|
48
- tools_config = server_config[:tools]
49
- mode = tools_config.nil? ? :discovery : :optimized
50
-
51
- # Emit event before initialization
52
- emit_mcp_init_start(agent_name, server_config, mode)
53
-
54
- client = initialize_mcp_client(server_config)
55
-
56
- # Store client for cleanup
57
- @mcp_clients[agent_name] << client
58
-
59
- if tools_config.nil?
60
- # Discovery mode: Fetch all tools from server (calls tools/list)
61
- # client.tools returns RubyLLM::Tool instances (already wrapped by internal Coordinator)
62
- all_tools = client.tools
63
- tool_names = all_tools.map { |t| t.respond_to?(:name) ? t.name : t.to_s }
64
-
65
- all_tools.each do |tool|
66
- chat.tool_registry.register(
67
- tool,
68
- source: :mcp,
69
- metadata: { server_name: server_config[:name] },
70
- )
71
- end
72
-
73
- # Emit completion event for discovery mode
74
- emit_mcp_init_complete(agent_name, server_config, mode, all_tools.size, tool_names)
75
- RubyLLM.logger.debug("SwarmSDK: Discovered and registered #{all_tools.size} tools from MCP server '#{server_config[:name]}'")
76
- else
77
- # Optimized mode: Create tool stubs without tools/list RPC (Plan 025)
78
- # Use client directly (it has internal coordinator)
79
- tool_names = tools_config.map(&:to_s)
80
-
81
- tools_config.each do |tool_name|
82
- stub = Tools::McpToolStub.new(
83
- client: client,
84
- name: tool_name.to_s,
85
- server_name: server_config[:name],
86
- )
87
- chat.tool_registry.register(
88
- stub,
89
- source: :mcp,
90
- metadata: { server_name: server_config[:name] },
91
- )
92
- end
93
-
94
- # Emit completion event for optimized mode
95
- emit_mcp_init_complete(agent_name, server_config, mode, tools_config.size, tool_names)
96
- RubyLLM.logger.debug("SwarmSDK: Registered #{tools_config.size} tool stubs from MCP server '#{server_config[:name]}' (lazy schema)")
97
- end
98
- rescue StandardError => e
99
- RubyLLM.logger.error("SwarmSDK: Failed to initialize MCP server '#{server_config[:name]}' for agent #{agent_name}: #{e.class.name}: #{e.message}")
100
- RubyLLM.logger.error("SwarmSDK: Backtrace: #{e.backtrace.first(5).join("\n ")}")
101
- raise ConfigurationError, "Failed to initialize MCP server '#{server_config[:name]}': #{e.message}"
102
- end
103
- end
104
-
105
- # Build transport-specific configuration for MCP client
106
- #
107
- # This method is public for testing delegation from Swarm.
108
- #
109
- # @param transport_type [Symbol] Transport type (:stdio, :sse, :streamable)
110
- # @param config [Hash] MCP server configuration
111
- # @return [Hash] Transport-specific configuration
112
- def build_transport_config(transport_type, config)
113
- case transport_type
114
- when :stdio
115
- build_stdio_config(config)
116
- when :sse
117
- build_sse_config(config)
118
- when :streamable
119
- build_streamable_config(config)
120
- else
121
- raise ArgumentError, "Unsupported transport type: #{transport_type}"
122
- end
123
- end
124
-
125
- private
126
-
127
- # Initialize an MCP client from configuration
128
- #
129
- # @param config [Hash] MCP server configuration
130
- # @return [RubyLLM::MCP::Client] Initialized MCP client
131
- def initialize_mcp_client(config)
132
- # Configure SSL before creating the client so HTTPX connections use the right options
133
- configure_mcp_ssl(config)
134
-
135
- # Convert timeout from seconds to milliseconds
136
- # Use explicit config[:timeout] if provided, otherwise use global default
137
- timeout_seconds = config[:timeout] || SwarmSDK.config.mcp_request_timeout
138
- timeout_ms = timeout_seconds * 1000
139
-
140
- # Determine transport type
141
- transport_type = determine_transport_type(config[:type])
142
-
143
- # Build transport-specific configuration
144
- client_config = build_transport_config(transport_type, config)
145
-
146
- # Create and start MCP client
147
- RubyLLM::MCP.client(
148
- name: config[:name],
149
- transport_type: transport_type,
150
- request_timeout: timeout_ms,
151
- config: client_config,
152
- )
153
- end
154
-
155
- # Determine transport type from configuration
156
- #
157
- # @param type [Symbol, String, nil] Transport type from config
158
- # @return [Symbol] Normalized transport type
159
- def determine_transport_type(type)
160
- case type&.to_sym
161
- when :stdio then :stdio
162
- when :sse then :sse
163
- when :http, :streamable then :streamable
164
- else
165
- raise ArgumentError, "Unknown MCP transport type: #{type}"
166
- end
167
- end
168
-
169
- # Build stdio transport configuration
170
- #
171
- # @param config [Hash] MCP server configuration
172
- # @return [Hash] Stdio configuration
173
- def build_stdio_config(config)
174
- {
175
- command: config[:command],
176
- args: config[:args] || [],
177
- env: Utils.stringify_keys(config[:env] || {}),
178
- }
179
- end
180
-
181
- # Build SSE transport configuration
182
- #
183
- # @param config [Hash] MCP server configuration
184
- # @return [Hash] SSE configuration
185
- def build_sse_config(config)
186
- sse_config = {
187
- url: config[:url],
188
- headers: config[:headers] || {},
189
- version: config[:version]&.to_sym || :http2,
190
- }
191
-
192
- # Add reconnection options for resilient SSE connections
193
- sse_config[:reconnection] = build_reconnection_options(config)
194
-
195
- sse_config
196
- end
197
-
198
- # Build streamable (HTTP) transport configuration
199
- #
200
- # @param config [Hash] MCP server configuration
201
- # @return [Hash] Streamable configuration
202
- def build_streamable_config(config)
203
- streamable_config = {
204
- url: config[:url],
205
- headers: config[:headers] || {},
206
- version: config[:version]&.to_sym || :http2,
207
- }
208
-
209
- # Only include rate_limit if present
210
- streamable_config[:rate_limit] = config[:rate_limit] if config[:rate_limit]
211
-
212
- # Add reconnection options for resilient streamable connections
213
- streamable_config[:reconnection] = build_reconnection_options(config)
214
-
215
- streamable_config
216
- end
217
-
218
- # Build reconnection options from config or defaults
219
- #
220
- # Provides exponential backoff reconnection for SSE/streamable transports.
221
- # Can be customized per-server or uses global defaults.
222
- #
223
- # @param config [Hash] MCP server configuration
224
- # @return [Hash] Reconnection options
225
- def build_reconnection_options(config)
226
- reconnection_config = config[:reconnection] || {}
227
-
228
- {
229
- max_retries: reconnection_config[:max_retries] || Defaults::McpReconnection::MAX_RETRIES,
230
- initial_reconnection_delay: reconnection_config[:initial_delay] || Defaults::McpReconnection::INITIAL_DELAY_MS,
231
- reconnection_delay_grow_factor: reconnection_config[:delay_grow_factor] || Defaults::McpReconnection::DELAY_GROW_FACTOR,
232
- max_reconnection_delay: reconnection_config[:max_delay] || Defaults::McpReconnection::MAX_DELAY_MS,
233
- }
234
- end
235
-
236
- # Configure SSL options for MCP HTTPX connections
237
- #
238
- # Sets McpSslPatch.ssl_options based on per-server ssl_verify config
239
- # or global SwarmSDK.config.mcp_ssl_verify. Resets the thread-local
240
- # connection cache so build_connection picks up the new options.
241
- #
242
- # @param config [Hash] MCP server configuration
243
- # @option config [Boolean] :ssl_verify Override global SSL verify setting
244
- # @return [void]
245
- def configure_mcp_ssl(config)
246
- ssl_verify = config.fetch(:ssl_verify, SwarmSDK.config.mcp_ssl_verify)
247
- verify_mode = ssl_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
248
-
249
- McpSslPatch.ssl_options = { verify_mode: verify_mode }
250
- McpSslPatch.reset_connection!
251
- end
252
-
253
- # Emit MCP server initialization start event
254
- #
255
- # @param agent_name [Symbol] Agent name
256
- # @param server_config [Hash] MCP server configuration
257
- # @param mode [Symbol] Initialization mode (:discovery or :optimized)
258
- # @return [void]
259
- def emit_mcp_init_start(agent_name, server_config, mode)
260
- LogStream.emit(
261
- type: "mcp_server_init_start",
262
- agent: agent_name,
263
- server_name: server_config[:name],
264
- transport_type: server_config[:type],
265
- mode: mode,
266
- )
267
- end
268
-
269
- # Emit MCP server initialization complete event
270
- #
271
- # @param agent_name [Symbol] Agent name
272
- # @param server_config [Hash] MCP server configuration
273
- # @param mode [Symbol] Initialization mode (:discovery or :optimized)
274
- # @param tool_count [Integer] Number of tools registered
275
- # @param tool_names [Array<String>] Names of registered tools
276
- # @return [void]
277
- def emit_mcp_init_complete(agent_name, server_config, mode, tool_count, tool_names)
278
- LogStream.emit(
279
- type: "mcp_server_init_complete",
280
- agent: agent_name,
281
- server_name: server_config[:name],
282
- transport_type: server_config[:type],
283
- mode: mode,
284
- tool_count: tool_count,
285
- tools: tool_names,
286
- )
287
- end
288
- end
289
- end
290
- end