swarm_sdk 2.7.13 → 3.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
  4. data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
  5. data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
  6. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  7. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  8. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  9. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  10. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  11. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  12. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  13. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  14. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  15. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  16. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  17. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  18. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  19. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  20. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  24. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  25. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  26. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  27. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  28. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  29. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  30. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  31. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  32. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  33. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  34. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  35. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  36. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  37. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  38. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  39. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  40. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  41. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  42. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  43. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  45. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  46. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  47. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  48. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  49. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  50. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  51. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  52. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  53. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  54. data/lib/swarm_sdk/v3.rb +145 -0
  55. metadata +84 -148
  56. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  57. data/lib/swarm_sdk/agent/builder.rb +0 -680
  58. data/lib/swarm_sdk/agent/chat.rb +0 -1432
  59. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  60. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  61. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  62. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  63. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  64. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  65. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  66. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  67. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  68. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  69. data/lib/swarm_sdk/agent/context.rb +0 -115
  70. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  71. data/lib/swarm_sdk/agent/definition.rb +0 -581
  72. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  73. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  74. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  75. data/lib/swarm_sdk/agent_registry.rb +0 -146
  76. data/lib/swarm_sdk/builders/base_builder.rb +0 -553
  77. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  78. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  79. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  80. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  81. data/lib/swarm_sdk/config.rb +0 -367
  82. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  83. data/lib/swarm_sdk/configuration/translator.rb +0 -283
  84. data/lib/swarm_sdk/configuration.rb +0 -165
  85. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  86. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  87. data/lib/swarm_sdk/context_compactor.rb +0 -335
  88. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  89. data/lib/swarm_sdk/context_management/context.rb +0 -328
  90. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  91. data/lib/swarm_sdk/defaults.rb +0 -251
  92. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  93. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  94. data/lib/swarm_sdk/hooks/context.rb +0 -197
  95. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  96. data/lib/swarm_sdk/hooks/error.rb +0 -29
  97. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  98. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  99. data/lib/swarm_sdk/hooks/result.rb +0 -150
  100. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  101. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  102. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  103. data/lib/swarm_sdk/log_collector.rb +0 -227
  104. data/lib/swarm_sdk/log_stream.rb +0 -127
  105. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  106. data/lib/swarm_sdk/model_aliases.json +0 -8
  107. data/lib/swarm_sdk/models.json +0 -44002
  108. data/lib/swarm_sdk/models.rb +0 -161
  109. data/lib/swarm_sdk/node_context.rb +0 -245
  110. data/lib/swarm_sdk/observer/builder.rb +0 -81
  111. data/lib/swarm_sdk/observer/config.rb +0 -45
  112. data/lib/swarm_sdk/observer/manager.rb +0 -236
  113. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  114. data/lib/swarm_sdk/permissions/config.rb +0 -239
  115. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  116. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  117. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  118. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  119. data/lib/swarm_sdk/plugin.rb +0 -309
  120. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  121. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  122. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  123. data/lib/swarm_sdk/restore_result.rb +0 -65
  124. data/lib/swarm_sdk/result.rb +0 -212
  125. data/lib/swarm_sdk/snapshot.rb +0 -156
  126. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  127. data/lib/swarm_sdk/state_restorer.rb +0 -476
  128. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  129. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  130. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
  131. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  132. data/lib/swarm_sdk/swarm/executor.rb +0 -290
  133. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
  134. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  135. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
  136. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
  137. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  138. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  139. data/lib/swarm_sdk/swarm.rb +0 -843
  140. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  141. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  142. data/lib/swarm_sdk/tools/base.rb +0 -63
  143. data/lib/swarm_sdk/tools/bash.rb +0 -280
  144. data/lib/swarm_sdk/tools/clock.rb +0 -46
  145. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  146. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  147. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  148. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  149. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  150. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  151. data/lib/swarm_sdk/tools/edit.rb +0 -145
  152. data/lib/swarm_sdk/tools/glob.rb +0 -166
  153. data/lib/swarm_sdk/tools/grep.rb +0 -235
  154. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  155. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  156. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  157. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -100
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  182. data/lib/swarm_sdk/workflow.rb +0 -589
  183. data/lib/swarm_sdk.rb +0 -718
@@ -1,328 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module ContextManagement
5
- # Rich context wrapper for context management handlers
6
- #
7
- # Provides a clean, developer-friendly API for manipulating the conversation
8
- # context when warning thresholds are triggered. Wraps the lower-level
9
- # Hooks::Context with message manipulation helpers.
10
- #
11
- # @example Basic usage in handler
12
- # on :warning_60 do |ctx|
13
- # ctx.compress_tool_results(keep_recent: 10)
14
- # end
15
- #
16
- # @example Advanced usage with metrics
17
- # on :warning_80 do |ctx|
18
- # if ctx.usage_percentage > 85
19
- # ctx.prune_old_messages(keep_recent: 10)
20
- # ctx.log_action("aggressive_pruning", remaining: ctx.tokens_remaining)
21
- # else
22
- # ctx.compress_tool_results(keep_recent: 5, truncate_to: 100)
23
- # end
24
- # end
25
- class Context
26
- # Create a new context wrapper
27
- #
28
- # @param hooks_context [Hooks::Context] Lower-level hook context with metadata
29
- def initialize(hooks_context)
30
- @hooks_context = hooks_context
31
- @chat = hooks_context.metadata[:chat]
32
- end
33
-
34
- # --- Context Metrics ---
35
-
36
- # Current context usage percentage
37
- #
38
- # @return [Float] Usage percentage (0.0 to 100.0)
39
- #
40
- # @example
41
- # if ctx.usage_percentage > 85
42
- # ctx.prune_old_messages(keep_recent: 10)
43
- # end
44
- def usage_percentage
45
- @hooks_context.metadata[:percentage]
46
- end
47
-
48
- # Threshold that triggered this handler
49
- #
50
- # @return [Integer] Threshold (60, 80, or 90)
51
- #
52
- # @example
53
- # ctx.log_action("threshold_hit", threshold: ctx.threshold)
54
- def threshold
55
- @hooks_context.metadata[:threshold]
56
- end
57
-
58
- # Total tokens used so far
59
- #
60
- # @return [Integer] Token count
61
- #
62
- # @example
63
- # ctx.log_action("usage", tokens: ctx.tokens_used)
64
- def tokens_used
65
- @hooks_context.metadata[:tokens_used]
66
- end
67
-
68
- # Tokens remaining in context window
69
- #
70
- # @return [Integer] Token count
71
- #
72
- # @example
73
- # if ctx.tokens_remaining < 10000
74
- # ctx.prune_old_messages(keep_recent: 5)
75
- # end
76
- def tokens_remaining
77
- @hooks_context.metadata[:tokens_remaining]
78
- end
79
-
80
- # Total context window size
81
- #
82
- # @return [Integer] Token count
83
- #
84
- # @example
85
- # buffer = ctx.context_limit * 0.1 # 10% buffer
86
- def context_limit
87
- @hooks_context.metadata[:context_limit]
88
- end
89
-
90
- # Agent name
91
- #
92
- # @return [Symbol] Agent identifier
93
- #
94
- # @example
95
- # ctx.log_action("agent_context", agent: ctx.agent_name)
96
- def agent_name
97
- @hooks_context.agent_name
98
- end
99
-
100
- # --- Message Access ---
101
-
102
- # Get all messages (copy for manipulation)
103
- #
104
- # @return [Array<RubyLLM::Message>] Message array
105
- #
106
- # @example
107
- # ctx.messages.each do |msg|
108
- # puts "#{msg.role}: #{msg.content.length} chars"
109
- # end
110
- def messages
111
- @chat.messages
112
- end
113
-
114
- # Number of messages
115
- #
116
- # @return [Integer] Message count
117
- #
118
- # @example
119
- # if ctx.message_count > 100
120
- # ctx.prune_old_messages(keep_recent: 50)
121
- # end
122
- def message_count
123
- @chat.message_count
124
- end
125
-
126
- # --- Message Manipulation ---
127
-
128
- # Replace all messages with new array
129
- #
130
- # @param new_messages [Array<RubyLLM::Message>] New message array
131
- # @return [void]
132
- #
133
- # @example
134
- # new_msgs = ctx.messages.reject { |m| m.role == :tool }
135
- # ctx.replace_messages(new_msgs)
136
- def replace_messages(new_messages)
137
- @chat.replace_messages(new_messages)
138
- end
139
-
140
- # Compress tool result messages to save context space
141
- #
142
- # Creates NEW message objects with truncated content (follows RubyLLM patterns).
143
- # Truncates old tool results while keeping recent ones intact.
144
- # Automatically marks compression as applied to prevent double compression.
145
- #
146
- # @param keep_recent [Integer] Number of recent tool results to preserve (default: 10)
147
- # @param truncate_to [Integer] Max characters for truncated results (default: 200)
148
- # @return [Integer] Number of messages compressed
149
- #
150
- # @example Light compression at 60%
151
- # ctx.compress_tool_results(keep_recent: 15, truncate_to: 500)
152
- #
153
- # @example Aggressive compression at 80%
154
- # ctx.compress_tool_results(keep_recent: 5, truncate_to: 100)
155
- def compress_tool_results(keep_recent: 10, truncate_to: 200)
156
- msgs = messages.dup
157
- compressed_count = 0
158
-
159
- # Find tool result messages (skip recent ones)
160
- tool_indices = []
161
- msgs.each_with_index do |msg, idx|
162
- tool_indices << idx if msg.role == :tool
163
- end
164
-
165
- # Keep recent tool results, compress older ones
166
- indices_to_compress = tool_indices[0...-keep_recent] || []
167
-
168
- indices_to_compress.each do |idx|
169
- msg = msgs[idx]
170
- content = msg.content.to_s
171
- next if content.length <= truncate_to
172
-
173
- # Create NEW message with truncated content (NO instance_variable_set!)
174
- truncated_content = "#{content[0...truncate_to]}... [truncated for context management]"
175
-
176
- # Create new message object following RubyLLM patterns
177
- msgs[idx] = RubyLLM::Message.new(
178
- role: :tool,
179
- content: truncated_content,
180
- tool_call_id: msg.tool_call_id,
181
- )
182
- compressed_count += 1
183
- end
184
-
185
- replace_messages(msgs)
186
-
187
- # Mark compression as applied to coordinate with ContextManager
188
- mark_compression_applied
189
-
190
- compressed_count
191
- end
192
-
193
- # Mark compression as applied in ContextManager
194
- #
195
- # Call this when your handler performs compression to prevent
196
- # double compression from auto-compression logic.
197
- #
198
- # @return [void]
199
- #
200
- # @example Custom compression
201
- # msgs = ctx.messages.map { |m| ... } # custom logic
202
- # ctx.replace_messages(msgs)
203
- # ctx.mark_compression_applied
204
- def mark_compression_applied
205
- return unless @chat.respond_to?(:context_manager)
206
-
207
- @chat.context_manager.compression_applied = true
208
- end
209
-
210
- # Check if compression has already been applied
211
- #
212
- # @return [Boolean] True if compression was already applied
213
- #
214
- # @example Conditional compression
215
- # unless ctx.compression_applied?
216
- # ctx.compress_tool_results(keep_recent: 10)
217
- # end
218
- def compression_applied?
219
- return false unless @chat.respond_to?(:context_manager)
220
-
221
- !!@chat.context_manager.compression_applied
222
- end
223
-
224
- # Remove old messages from history
225
- #
226
- # Keeps system message (if any) and recent exchanges.
227
- # This is more aggressive than compression and loses context.
228
- #
229
- # @param keep_recent [Integer] Number of recent messages to keep (default: 20)
230
- # @return [Integer] Number of messages removed
231
- #
232
- # @example Prune at 80% threshold
233
- # ctx.prune_old_messages(keep_recent: 30)
234
- #
235
- # @example Emergency pruning at 90%
236
- # ctx.prune_old_messages(keep_recent: 10)
237
- def prune_old_messages(keep_recent: 20)
238
- msgs = messages.dup
239
- original_count = msgs.size
240
-
241
- # Always keep system message if present
242
- system_msg = msgs.first if msgs.first&.role == :system
243
- non_system = system_msg ? msgs[1..] : msgs
244
-
245
- # Keep only recent messages
246
- if non_system.size > keep_recent
247
- kept = non_system.last(keep_recent)
248
- new_msgs = system_msg ? [system_msg] + kept : kept
249
- replace_messages(new_msgs)
250
- original_count - new_msgs.size
251
- else
252
- 0
253
- end
254
- end
255
-
256
- # Summarize old message exchanges
257
- #
258
- # Groups old user/assistant pairs and replaces with summary.
259
- # This is a placeholder - actual implementation would use LLM.
260
- #
261
- # @param older_than [Integer] Messages older than this index get summarized
262
- # @return [Integer] Number of exchanges summarized
263
- #
264
- # @example
265
- # ctx.summarize_old_exchanges(older_than: 10)
266
- def summarize_old_exchanges(older_than: 10)
267
- # For now, this is a marker - full implementation would call LLM
268
- # to summarize exchanges. We provide the API for developers to
269
- # implement their own summarization logic.
270
- 0
271
- end
272
-
273
- # Custom message transformation
274
- #
275
- # Apply a block to transform messages. This gives full control
276
- # over message manipulation for custom strategies.
277
- #
278
- # @yield [Array<RubyLLM::Message>] Current messages
279
- # @yieldreturn [Array<RubyLLM::Message>] Transformed messages
280
- # @return [void]
281
- #
282
- # @example Remove specific tool results
283
- # ctx.transform_messages do |msgs|
284
- # msgs.reject { |m| m.role == :tool && m.content.include?("verbose output") }
285
- # end
286
- #
287
- # @example Custom compression logic
288
- # ctx.transform_messages do |msgs|
289
- # msgs.map do |m|
290
- # if m.role == :tool && m.content.length > 1000
291
- # RubyLLM::Message.new(role: :tool, content: m.content[0..500], tool_call_id: m.tool_call_id)
292
- # else
293
- # m
294
- # end
295
- # end
296
- # end
297
- def transform_messages
298
- new_msgs = yield(messages.dup)
299
- replace_messages(new_msgs)
300
- end
301
-
302
- # Log a context management action
303
- #
304
- # Emits a log event for tracking what actions were taken.
305
- # Useful for debugging and monitoring context management strategies.
306
- #
307
- # @param action [String] Description of action taken
308
- # @param details [Hash] Additional details
309
- # @return [void]
310
- #
311
- # @example Log compression action
312
- # ctx.log_action("compressed_tool_results", count: 5)
313
- #
314
- # @example Log emergency action
315
- # ctx.log_action("emergency_pruning", remaining: ctx.tokens_remaining)
316
- def log_action(action, details = {})
317
- LogStream.emit(
318
- type: "context_management_action",
319
- agent: agent_name,
320
- threshold: threshold,
321
- action: action,
322
- usage_percentage: usage_percentage,
323
- **details,
324
- )
325
- end
326
- end
327
- end
328
- end
@@ -1,226 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "delegate"
4
-
5
- module SwarmSDK
6
- # Registry for user-defined custom tools
7
- #
8
- # Provides a simple way to register custom tools without creating a full plugin.
9
- # Custom tools are registered globally and available to all agents that request them.
10
- #
11
- # ## When to Use Custom Tools vs Plugins
12
- #
13
- # **Use Custom Tools when:**
14
- # - You have simple, stateless tools
15
- # - Tools don't need persistent storage
16
- # - Tools don't need lifecycle hooks
17
- # - Tools don't need system prompt contributions
18
- #
19
- # **Use Plugins when:**
20
- # - Tools need persistent storage per agent
21
- # - Tools need lifecycle hooks (on_agent_initialized, on_user_message, etc.)
22
- # - Tools need to contribute to system prompts
23
- # - You have a suite of related tools that share configuration
24
- #
25
- # @example Register a simple tool
26
- # class WeatherTool < RubyLLM::Tool
27
- # description "Get weather for a city"
28
- # param :city, type: "string", required: true
29
- #
30
- # def execute(city:)
31
- # "Weather in #{city}: Sunny"
32
- # end
33
- # end
34
- #
35
- # SwarmSDK.register_tool(WeatherTool)
36
- #
37
- # @example Register with explicit name
38
- # SwarmSDK.register_tool(:Weather, WeatherTool)
39
- #
40
- # @example Tool with creation requirements
41
- # class AgentAwareTool < RubyLLM::Tool
42
- # def self.creation_requirements
43
- # [:agent_name, :directory]
44
- # end
45
- #
46
- # def initialize(agent_name:, directory:)
47
- # super()
48
- # @agent_name = agent_name
49
- # @directory = directory
50
- # end
51
- #
52
- # def execute
53
- # "Agent: #{@agent_name}, Dir: #{@directory}"
54
- # end
55
- # end
56
- #
57
- # SwarmSDK.register_tool(AgentAwareTool)
58
- #
59
- module CustomToolRegistry
60
- # Wrapper that overrides the tool's name to match the registered name
61
- #
62
- # This ensures that when a user registers a tool with a specific name,
63
- # that name is what gets used for tool lookup (has_tool?) and LLM tool calls.
64
- class NamedToolWrapper < SimpleDelegator
65
- def initialize(tool, registered_name)
66
- super(tool)
67
- @registered_name = registered_name.to_s
68
- end
69
-
70
- # Override name to return the registered name
71
- def name
72
- @registered_name
73
- end
74
- end
75
-
76
- @tools = {}
77
-
78
- class << self
79
- # Register a custom tool
80
- #
81
- # @param name [Symbol] Tool name
82
- # @param tool_class [Class] Tool class (must be a RubyLLM::Tool subclass)
83
- # @raise [ArgumentError] If tool_class is not a RubyLLM::Tool subclass
84
- # @raise [ArgumentError] If a tool with the same name is already registered
85
- # @return [void]
86
- def register(name, tool_class)
87
- name = name.to_sym
88
-
89
- unless tool_class.is_a?(Class) && tool_class < RubyLLM::Tool
90
- raise ArgumentError, "Tool class must inherit from RubyLLM::Tool"
91
- end
92
-
93
- if @tools.key?(name)
94
- raise ArgumentError, "Custom tool '#{name}' is already registered"
95
- end
96
-
97
- if PluginRegistry.plugin_tool?(name)
98
- raise ArgumentError, "Tool '#{name}' is already provided by a plugin"
99
- end
100
-
101
- if Tools::Registry.exists?(name)
102
- raise ArgumentError, "Tool '#{name}' is a built-in tool and cannot be overridden"
103
- end
104
-
105
- @tools[name] = tool_class
106
- end
107
-
108
- # Check if a custom tool is registered
109
- #
110
- # @param name [Symbol, String] Tool name
111
- # @return [Boolean]
112
- def registered?(name)
113
- @tools.key?(name.to_sym)
114
- end
115
-
116
- # Get a registered tool class
117
- #
118
- # @param name [Symbol, String] Tool name
119
- # @return [Class, nil] Tool class or nil if not found
120
- def get(name)
121
- @tools[name.to_sym]
122
- end
123
-
124
- # Get all registered custom tool names
125
- #
126
- # @return [Array<Symbol>]
127
- def tool_names
128
- @tools.keys
129
- end
130
-
131
- # Create a tool instance
132
- #
133
- # Uses the tool's `creation_requirements` class method (if defined) to determine
134
- # what parameters to pass to the constructor. The created tool is wrapped with
135
- # NamedToolWrapper to ensure the registered name is used for tool lookup.
136
- #
137
- # @param name [Symbol, String] Tool name
138
- # @param context [Hash] Available context for tool creation
139
- # @option context [Symbol] :agent_name Agent identifier
140
- # @option context [String] :directory Agent's working directory
141
- # @return [RubyLLM::Tool] Instantiated tool (wrapped with registered name)
142
- # @raise [ConfigurationError] If tool is unknown or has unmet requirements
143
- def create(name, context = {})
144
- name_sym = name.to_sym
145
- tool_class = @tools[name_sym]
146
-
147
- raise ConfigurationError, "Unknown custom tool: #{name}" unless tool_class
148
-
149
- # Create the tool instance
150
- tool = if tool_class.respond_to?(:creation_requirements)
151
- requirements = tool_class.creation_requirements
152
- params = extract_params(requirements, context, name)
153
- tool_class.new(**params)
154
- else
155
- # No requirements - simple instantiation
156
- tool_class.new
157
- end
158
-
159
- # Wrap with NamedToolWrapper to ensure registered name is used
160
- NamedToolWrapper.new(tool, name_sym)
161
- end
162
-
163
- # Unregister a custom tool
164
- #
165
- # @param name [Symbol, String] Tool name
166
- # @return [Class, nil] The unregistered tool class, or nil if not found
167
- def unregister(name)
168
- @tools.delete(name.to_sym)
169
- end
170
-
171
- # Clear all registered custom tools
172
- #
173
- # Primarily useful for testing.
174
- #
175
- # @return [void]
176
- def clear
177
- @tools.clear
178
- end
179
-
180
- # Infer tool name from class name
181
- #
182
- # @param tool_class [Class] Tool class
183
- # @return [Symbol] Inferred tool name
184
- #
185
- # @example
186
- # infer_name(WeatherTool) #=> :Weather
187
- # infer_name(MyApp::Tools::StockPrice) #=> :StockPrice
188
- # infer_name(MyApp::Tools::StockPriceTool) #=> :StockPrice
189
- def infer_name(tool_class)
190
- # Get the class name without module prefix
191
- class_name = tool_class.name.split("::").last
192
-
193
- # Remove "Tool" suffix if present
194
- name = class_name.sub(/Tool\z/, "")
195
-
196
- name.to_sym
197
- end
198
-
199
- private
200
-
201
- # Extract required parameters from context
202
- #
203
- # @param requirements [Array<Symbol>] Required parameter names
204
- # @param context [Hash] Available context
205
- # @param tool_name [Symbol] Tool name for error messages
206
- # @return [Hash] Parameters to pass to tool constructor
207
- # @raise [ConfigurationError] If required parameter is missing
208
- def extract_params(requirements, context, tool_name)
209
- params = {}
210
-
211
- requirements.each do |req|
212
- unless context.key?(req)
213
- raise ConfigurationError,
214
- "Custom tool '#{tool_name}' requires '#{req}' but it was not provided. " \
215
- "Ensure the tool's `creation_requirements` only includes supported keys: " \
216
- ":agent_name, :directory"
217
- end
218
-
219
- params[req] = context[req]
220
- end
221
-
222
- params
223
- end
224
- end
225
- end
226
- end