swarm_sdk 2.7.14 → 3.0.0.alpha2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +16 -0
- data/lib/swarm_sdk/ruby_llm_patches/init.rb +4 -1
- data/lib/swarm_sdk/v3/agent.rb +1165 -0
- data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
- data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
- data/lib/swarm_sdk/v3/configuration.rb +490 -0
- data/lib/swarm_sdk/v3/debug_log.rb +86 -0
- data/lib/swarm_sdk/v3/event_stream.rb +130 -0
- data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
- data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
- data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
- data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
- data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
- data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
- data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
- data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
- data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
- data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
- data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
- data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
- data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
- data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
- data/lib/swarm_sdk/v3/memory/card.rb +206 -0
- data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
- data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
- data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
- data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
- data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
- data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
- data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
- data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
- data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
- data/lib/swarm_sdk/v3/memory/store.rb +489 -0
- data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
- data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
- data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
- data/lib/swarm_sdk/v3/tools/base.rb +80 -0
- data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
- data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
- data/lib/swarm_sdk/v3/tools/document_converters/base.rb +84 -0
- data/lib/swarm_sdk/v3/tools/document_converters/docx_converter.rb +120 -0
- data/lib/swarm_sdk/v3/tools/document_converters/pdf_converter.rb +111 -0
- data/lib/swarm_sdk/v3/tools/document_converters/xlsx_converter.rb +128 -0
- data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
- data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
- data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
- data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
- data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
- data/lib/swarm_sdk/v3/tools/read.rb +213 -0
- data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
- data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
- data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
- data/lib/swarm_sdk/v3/tools/think.rb +88 -0
- data/lib/swarm_sdk/v3/tools/write.rb +87 -0
- data/lib/swarm_sdk/v3.rb +145 -0
- metadata +88 -149
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
- data/lib/swarm_sdk/agent/builder.rb +0 -705
- data/lib/swarm_sdk/agent/chat.rb +0 -1438
- data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
- data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
- data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
- data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
- data/lib/swarm_sdk/agent/context.rb +0 -115
- data/lib/swarm_sdk/agent/context_manager.rb +0 -315
- data/lib/swarm_sdk/agent/definition.rb +0 -588
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
- data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
- data/lib/swarm_sdk/agent_registry.rb +0 -146
- data/lib/swarm_sdk/builders/base_builder.rb +0 -558
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
- data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
- data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
- data/lib/swarm_sdk/concerns/validatable.rb +0 -55
- data/lib/swarm_sdk/config.rb +0 -368
- data/lib/swarm_sdk/configuration/parser.rb +0 -397
- data/lib/swarm_sdk/configuration/translator.rb +0 -285
- data/lib/swarm_sdk/configuration.rb +0 -165
- data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
- data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
- data/lib/swarm_sdk/context_compactor.rb +0 -335
- data/lib/swarm_sdk/context_management/builder.rb +0 -128
- data/lib/swarm_sdk/context_management/context.rb +0 -328
- data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
- data/lib/swarm_sdk/defaults.rb +0 -251
- data/lib/swarm_sdk/events_to_messages.rb +0 -199
- data/lib/swarm_sdk/hooks/adapter.rb +0 -359
- data/lib/swarm_sdk/hooks/context.rb +0 -197
- data/lib/swarm_sdk/hooks/definition.rb +0 -80
- data/lib/swarm_sdk/hooks/error.rb +0 -29
- data/lib/swarm_sdk/hooks/executor.rb +0 -146
- data/lib/swarm_sdk/hooks/registry.rb +0 -147
- data/lib/swarm_sdk/hooks/result.rb +0 -150
- data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
- data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
- data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
- data/lib/swarm_sdk/log_collector.rb +0 -227
- data/lib/swarm_sdk/log_stream.rb +0 -127
- data/lib/swarm_sdk/markdown_parser.rb +0 -75
- data/lib/swarm_sdk/model_aliases.json +0 -8
- data/lib/swarm_sdk/models.json +0 -44002
- data/lib/swarm_sdk/models.rb +0 -161
- data/lib/swarm_sdk/node_context.rb +0 -245
- data/lib/swarm_sdk/observer/builder.rb +0 -81
- data/lib/swarm_sdk/observer/config.rb +0 -45
- data/lib/swarm_sdk/observer/manager.rb +0 -248
- data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
- data/lib/swarm_sdk/permissions/config.rb +0 -239
- data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
- data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
- data/lib/swarm_sdk/permissions/validator.rb +0 -173
- data/lib/swarm_sdk/permissions_builder.rb +0 -122
- data/lib/swarm_sdk/plugin.rb +0 -309
- data/lib/swarm_sdk/plugin_registry.rb +0 -101
- data/lib/swarm_sdk/proc_helpers.rb +0 -53
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
- data/lib/swarm_sdk/restore_result.rb +0 -65
- data/lib/swarm_sdk/result.rb +0 -241
- data/lib/swarm_sdk/snapshot.rb +0 -156
- data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
- data/lib/swarm_sdk/state_restorer.rb +0 -476
- data/lib/swarm_sdk/state_snapshot.rb +0 -334
- data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
- data/lib/swarm_sdk/swarm/builder.rb +0 -256
- data/lib/swarm_sdk/swarm/executor.rb +0 -446
- data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
- data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
- data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
- data/lib/swarm_sdk/swarm.rb +0 -973
- data/lib/swarm_sdk/swarm_loader.rb +0 -145
- data/lib/swarm_sdk/swarm_registry.rb +0 -136
- data/lib/swarm_sdk/tools/base.rb +0 -63
- data/lib/swarm_sdk/tools/bash.rb +0 -280
- data/lib/swarm_sdk/tools/clock.rb +0 -46
- data/lib/swarm_sdk/tools/delegate.rb +0 -389
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
- data/lib/swarm_sdk/tools/edit.rb +0 -145
- data/lib/swarm_sdk/tools/glob.rb +0 -166
- data/lib/swarm_sdk/tools/grep.rb +0 -235
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
- data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
- data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
- data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
- data/lib/swarm_sdk/tools/read.rb +0 -261
- data/lib/swarm_sdk/tools/registry.rb +0 -205
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
- data/lib/swarm_sdk/tools/think.rb +0 -100
- data/lib/swarm_sdk/tools/todo_write.rb +0 -237
- data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
- data/lib/swarm_sdk/tools/write.rb +0 -112
- data/lib/swarm_sdk/transcript_builder.rb +0 -278
- data/lib/swarm_sdk/utils.rb +0 -68
- data/lib/swarm_sdk/validation_result.rb +0 -33
- data/lib/swarm_sdk/version.rb +0 -5
- data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
- data/lib/swarm_sdk/workflow/builder.rb +0 -227
- data/lib/swarm_sdk/workflow/executor.rb +0 -497
- data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
- data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
- data/lib/swarm_sdk/workflow.rb +0 -589
- 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
|