swarm_memory 2.1.5 → 2.1.6

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 (182) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. metadata +5 -184
  4. data/lib/claude_swarm/base_executor.rb +0 -133
  5. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  6. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  7. data/lib/claude_swarm/cli.rb +0 -697
  8. data/lib/claude_swarm/commands/ps.rb +0 -215
  9. data/lib/claude_swarm/commands/show.rb +0 -139
  10. data/lib/claude_swarm/configuration.rb +0 -373
  11. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  12. data/lib/claude_swarm/json_handler.rb +0 -91
  13. data/lib/claude_swarm/mcp_generator.rb +0 -230
  14. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  15. data/lib/claude_swarm/openai/executor.rb +0 -256
  16. data/lib/claude_swarm/openai/responses.rb +0 -319
  17. data/lib/claude_swarm/orchestrator.rb +0 -878
  18. data/lib/claude_swarm/process_tracker.rb +0 -78
  19. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  20. data/lib/claude_swarm/session_path.rb +0 -42
  21. data/lib/claude_swarm/settings_generator.rb +0 -77
  22. data/lib/claude_swarm/system_utils.rb +0 -46
  23. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  24. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  25. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  27. data/lib/claude_swarm/version.rb +0 -5
  28. data/lib/claude_swarm/worktree_manager.rb +0 -475
  29. data/lib/claude_swarm/yaml_loader.rb +0 -22
  30. data/lib/claude_swarm.rb +0 -67
  31. data/lib/swarm_cli/cli.rb +0 -201
  32. data/lib/swarm_cli/command_registry.rb +0 -61
  33. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  34. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  35. data/lib/swarm_cli/commands/migrate.rb +0 -55
  36. data/lib/swarm_cli/commands/run.rb +0 -173
  37. data/lib/swarm_cli/config_loader.rb +0 -98
  38. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  39. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  40. data/lib/swarm_cli/interactive_repl.rb +0 -924
  41. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  42. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  43. data/lib/swarm_cli/migrate_options.rb +0 -54
  44. data/lib/swarm_cli/migrator.rb +0 -132
  45. data/lib/swarm_cli/options.rb +0 -151
  46. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  47. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  48. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  49. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  50. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  51. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  52. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  53. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  54. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  55. data/lib/swarm_cli/ui/icons.rb +0 -36
  56. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  57. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  58. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  59. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  60. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  61. data/lib/swarm_cli/version.rb +0 -5
  62. data/lib/swarm_cli.rb +0 -46
  63. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  64. data/lib/swarm_sdk/agent/builder.rb +0 -552
  65. data/lib/swarm_sdk/agent/chat.rb +0 -774
  66. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  67. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  68. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  69. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  70. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  71. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  72. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  73. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  75. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  76. data/lib/swarm_sdk/agent/context.rb +0 -116
  77. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  78. data/lib/swarm_sdk/agent/definition.rb +0 -477
  79. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  80. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  81. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  82. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  83. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  84. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  85. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  86. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  87. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  88. data/lib/swarm_sdk/configuration.rb +0 -135
  89. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  90. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  91. data/lib/swarm_sdk/context_compactor.rb +0 -335
  92. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  93. data/lib/swarm_sdk/context_management/context.rb +0 -328
  94. data/lib/swarm_sdk/defaults.rb +0 -196
  95. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  96. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  97. data/lib/swarm_sdk/hooks/context.rb +0 -197
  98. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  99. data/lib/swarm_sdk/hooks/error.rb +0 -29
  100. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  101. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  102. data/lib/swarm_sdk/hooks/result.rb +0 -150
  103. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  104. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  105. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  106. data/lib/swarm_sdk/log_collector.rb +0 -227
  107. data/lib/swarm_sdk/log_stream.rb +0 -127
  108. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  109. data/lib/swarm_sdk/model_aliases.json +0 -8
  110. data/lib/swarm_sdk/models.json +0 -1
  111. data/lib/swarm_sdk/models.rb +0 -120
  112. data/lib/swarm_sdk/node_context.rb +0 -245
  113. data/lib/swarm_sdk/observer/builder.rb +0 -81
  114. data/lib/swarm_sdk/observer/config.rb +0 -45
  115. data/lib/swarm_sdk/observer/manager.rb +0 -236
  116. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  117. data/lib/swarm_sdk/permissions/config.rb +0 -239
  118. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  119. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  120. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  121. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  122. data/lib/swarm_sdk/plugin.rb +0 -309
  123. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  124. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  125. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  126. data/lib/swarm_sdk/restore_result.rb +0 -65
  127. data/lib/swarm_sdk/result.rb +0 -123
  128. data/lib/swarm_sdk/snapshot.rb +0 -156
  129. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  130. data/lib/swarm_sdk/state_restorer.rb +0 -476
  131. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  132. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  133. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  134. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  135. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  136. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  141. data/lib/swarm_sdk/swarm.rb +0 -717
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/bash.rb +0 -282
  145. data/lib/swarm_sdk/tools/clock.rb +0 -44
  146. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  147. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  148. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  149. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  150. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  151. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  152. data/lib/swarm_sdk/tools/edit.rb +0 -145
  153. data/lib/swarm_sdk/tools/glob.rb +0 -166
  154. data/lib/swarm_sdk/tools/grep.rb +0 -235
  155. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  156. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  157. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  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 -272
  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 -98
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/utils.rb +0 -68
  174. data/lib/swarm_sdk/validation_result.rb +0 -33
  175. data/lib/swarm_sdk/version.rb +0 -5
  176. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  177. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  178. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  179. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  180. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  181. data/lib/swarm_sdk/workflow.rb +0 -554
  182. data/lib/swarm_sdk.rb +0 -524
@@ -1,267 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- # Delegate tool for working with other agents in the swarm
6
- #
7
- # Creates agent-specific collaboration tools (e.g., WorkWithBackend)
8
- # that allow one agent to work with another agent.
9
- # Supports pre/post delegation hooks for customization.
10
- class Delegate < RubyLLM::Tool
11
- # Tool name prefix for delegation tools
12
- # Change this to customize the tool naming pattern (e.g., "DelegateTaskTo", "AskAgent", etc.)
13
- TOOL_NAME_PREFIX = "WorkWith"
14
-
15
- class << self
16
- # Generate tool name for a delegate agent
17
- #
18
- # This is the single source of truth for delegation tool naming.
19
- # Used both when creating Delegate instances and when predicting tool names
20
- # for agent context setup.
21
- #
22
- # @param delegate_name [String, Symbol] Name of the delegate agent
23
- # @return [String] Tool name (e.g., "WorkWithBackend")
24
- def tool_name_for(delegate_name)
25
- "#{TOOL_NAME_PREFIX}#{delegate_name.to_s.capitalize}"
26
- end
27
- end
28
-
29
- attr_reader :delegate_name, :delegate_target, :tool_name
30
-
31
- # Initialize a delegation tool
32
- #
33
- # @param delegate_name [String] Name of the delegate agent (e.g., "backend")
34
- # @param delegate_description [String] Description of the delegate agent
35
- # @param delegate_chat [AgentChat, nil] The chat instance for the delegate agent (nil if delegating to swarm)
36
- # @param agent_name [Symbol, String] Name of the agent using this tool
37
- # @param swarm [Swarm] The swarm instance (provides hook_registry, delegation_call_stack, swarm_registry)
38
- # @param delegating_chat [Agent::Chat, nil] The chat instance of the agent doing the delegating (for accessing hooks)
39
- def initialize(
40
- delegate_name:,
41
- delegate_description:,
42
- delegate_chat:,
43
- agent_name:,
44
- swarm:,
45
- delegating_chat: nil
46
- )
47
- super()
48
-
49
- @delegate_name = delegate_name
50
- @delegate_description = delegate_description
51
- @delegate_chat = delegate_chat
52
- @agent_name = agent_name
53
- @swarm = swarm
54
- @delegating_chat = delegating_chat
55
-
56
- # Generate tool name using canonical method
57
- @tool_name = self.class.tool_name_for(delegate_name)
58
- @delegate_target = delegate_name.to_s
59
- end
60
-
61
- # Override description to return dynamic string based on delegate
62
- def description
63
- "Work with #{@delegate_name} to delegate work, ask questions, or collaborate. #{@delegate_description}"
64
- end
65
-
66
- param :message,
67
- type: "string",
68
- desc: "Message to send to the agent - can be a work request, question, or collaboration message",
69
- required: true
70
-
71
- # Override name to return custom delegation tool name
72
- def name
73
- @tool_name
74
- end
75
-
76
- # Execute delegation with pre/post hooks
77
- #
78
- # @param message [String] Message to send to the agent
79
- # @return [String] Result from delegate agent or error message
80
- def execute(message:)
81
- # Access swarm infrastructure
82
- call_stack = @swarm.delegation_call_stack
83
- hook_registry = @swarm.hook_registry
84
- swarm_registry = @swarm.swarm_registry
85
-
86
- # Check for circular dependency
87
- if call_stack.include?(@delegate_target)
88
- emit_circular_warning(call_stack)
89
- return "Error: Circular delegation detected: #{call_stack.join(" -> ")} -> #{@delegate_target}. " \
90
- "Please restructure your delegation to avoid infinite loops."
91
- end
92
-
93
- # Get agent-specific hooks from the delegating chat instance
94
- agent_hooks = if @delegating_chat&.respond_to?(:hook_agent_hooks)
95
- @delegating_chat.hook_agent_hooks || {}
96
- else
97
- {}
98
- end
99
-
100
- # Trigger pre_delegation callback
101
- context = Hooks::Context.new(
102
- event: :pre_delegation,
103
- agent_name: @agent_name,
104
- swarm: @swarm,
105
- delegation_target: @delegate_target,
106
- metadata: {
107
- tool_name: @tool_name,
108
- message: message,
109
- timestamp: Time.now.utc.iso8601,
110
- },
111
- )
112
-
113
- executor = Hooks::Executor.new(hook_registry, logger: RubyLLM.logger)
114
- pre_agent_hooks = agent_hooks[:pre_delegation] || []
115
- result = executor.execute_safe(event: :pre_delegation, context: context, callbacks: pre_agent_hooks)
116
-
117
- # Check if callback halted or replaced the delegation
118
- if result.halt?
119
- return result.value || "Delegation halted by callback"
120
- elsif result.replace?
121
- return result.value
122
- end
123
-
124
- # Determine delegation type and proceed
125
- delegation_result = if @delegate_chat
126
- # Delegate to agent
127
- delegate_to_agent(message, call_stack)
128
- elsif swarm_registry&.registered?(@delegate_target)
129
- # Delegate to registered swarm
130
- delegate_to_swarm(message, call_stack, swarm_registry)
131
- else
132
- raise ConfigurationError, "Unknown delegation target: #{@delegate_target}"
133
- end
134
-
135
- # Trigger post_delegation callback
136
- post_context = Hooks::Context.new(
137
- event: :post_delegation,
138
- agent_name: @agent_name,
139
- swarm: @swarm,
140
- delegation_target: @delegate_target,
141
- delegation_result: delegation_result,
142
- metadata: {
143
- tool_name: @tool_name,
144
- message: message,
145
- result: delegation_result,
146
- timestamp: Time.now.utc.iso8601,
147
- },
148
- )
149
-
150
- post_agent_hooks = agent_hooks[:post_delegation] || []
151
- post_result = executor.execute_safe(event: :post_delegation, context: post_context, callbacks: post_agent_hooks)
152
-
153
- # Return modified result if callback replaces it
154
- if post_result.replace?
155
- post_result.value
156
- else
157
- delegation_result
158
- end
159
- rescue Faraday::TimeoutError, Net::ReadTimeout => e
160
- # Log timeout error as JSON event
161
- LogStream.emit(
162
- type: "delegation_error",
163
- agent: @agent_name,
164
- swarm_id: @swarm.swarm_id,
165
- parent_swarm_id: @swarm.parent_swarm_id,
166
- delegate_to: @tool_name,
167
- error_class: e.class.name,
168
- error_message: "Request timed out",
169
- error_backtrace: e.backtrace&.first(5) || [],
170
- )
171
- "Error: Request to #{@tool_name} timed out. The agent may be overloaded or the LLM service is not responding. Please try again or simplify the task."
172
- rescue Faraday::Error => e
173
- # Log network error as JSON event
174
- LogStream.emit(
175
- type: "delegation_error",
176
- agent: @agent_name,
177
- swarm_id: @swarm.swarm_id,
178
- parent_swarm_id: @swarm.parent_swarm_id,
179
- delegate_to: @tool_name,
180
- error_class: e.class.name,
181
- error_message: e.message,
182
- error_backtrace: e.backtrace&.first(5) || [],
183
- )
184
- "Error: Network error communicating with #{@tool_name}: #{e.class.name}. Please check connectivity and try again."
185
- rescue StandardError => e
186
- # Log unexpected error as JSON event
187
- backtrace_array = e.backtrace&.first(5) || []
188
- LogStream.emit(
189
- type: "delegation_error",
190
- agent: @agent_name,
191
- swarm_id: @swarm.swarm_id,
192
- parent_swarm_id: @swarm.parent_swarm_id,
193
- delegate_to: @tool_name,
194
- error_class: e.class.name,
195
- error_message: e.message,
196
- error_backtrace: backtrace_array,
197
- )
198
- # Return error string for LLM
199
- backtrace_str = backtrace_array.join("\n ")
200
- "Error: #{@tool_name} encountered an error: #{e.class.name}: #{e.message}\nBacktrace:\n #{backtrace_str}"
201
- end
202
-
203
- private
204
-
205
- # Delegate to an agent
206
- #
207
- # @param message [String] Message to send to the agent
208
- # @param call_stack [Array] Delegation call stack for circular dependency detection
209
- # @return [String] Result from agent
210
- def delegate_to_agent(message, call_stack)
211
- # Push delegate target onto call stack to track delegation chain
212
- call_stack.push(@delegate_target)
213
- begin
214
- response = @delegate_chat.ask(message, source: "delegation")
215
- response.content
216
- ensure
217
- # Always pop from stack, even if delegation fails
218
- call_stack.pop
219
- end
220
- end
221
-
222
- # Delegate to a registered swarm
223
- #
224
- # @param message [String] Message to send to the swarm
225
- # @param call_stack [Array] Delegation call stack for circular dependency detection
226
- # @param swarm_registry [SwarmRegistry] Registry for sub-swarms
227
- # @return [String] Result from swarm's lead agent
228
- def delegate_to_swarm(message, call_stack, swarm_registry)
229
- # Load sub-swarm (lazy load + cache)
230
- subswarm = swarm_registry.load_swarm(@delegate_target)
231
-
232
- # Push delegate target onto call stack to track delegation chain
233
- call_stack.push(@delegate_target)
234
- begin
235
- # Execute sub-swarm's lead agent
236
- lead_agent = subswarm.agents[subswarm.lead_agent]
237
- response = lead_agent.ask(message, source: "delegation")
238
- result = response.content
239
-
240
- # Reset if keep_context: false
241
- swarm_registry.reset_if_needed(@delegate_target)
242
-
243
- result
244
- ensure
245
- # Always pop from stack, even if delegation fails
246
- call_stack.pop
247
- end
248
- end
249
-
250
- # Emit circular dependency warning event
251
- #
252
- # @param call_stack [Array] Current delegation call stack
253
- # @return [void]
254
- def emit_circular_warning(call_stack)
255
- LogStream.emit(
256
- type: "delegation_circular_dependency",
257
- agent: @agent_name,
258
- swarm_id: @swarm.swarm_id,
259
- parent_swarm_id: @swarm.parent_swarm_id,
260
- target: @delegate_target,
261
- call_stack: call_stack,
262
- timestamp: Time.now.utc.iso8601,
263
- )
264
- end
265
- end
266
- end
267
- end
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- module DocumentConverters
6
- # Base class for document converters
7
- # Provides common interface and utility methods for converting various document formats
8
- class BaseConverter
9
- class << self
10
- # The gem name required for this converter
11
- # @return [String]
12
- def gem_name
13
- raise NotImplementedError, "#{name} must implement .gem_name"
14
- end
15
-
16
- # Human-readable format name
17
- # @return [String]
18
- def format_name
19
- raise NotImplementedError, "#{name} must implement .format_name"
20
- end
21
-
22
- # File extensions this converter handles
23
- # @return [Array<String>]
24
- def extensions
25
- raise NotImplementedError, "#{name} must implement .extensions"
26
- end
27
-
28
- # Check if the required gem is available
29
- # @return [Boolean]
30
- def available?
31
- gem_available?(gem_name)
32
- end
33
-
34
- # Check if a gem is installed
35
- # @param gem_name [String] Name of the gem to check
36
- # @return [Boolean]
37
- def gem_available?(gem_name)
38
- Gem::Specification.find_by_name(gem_name)
39
- true
40
- rescue Gem::LoadError
41
- false
42
- end
43
- end
44
-
45
- # Convert a document file to text/content
46
- # @param file_path [String] Path to the file
47
- # @return [String, RubyLLM::Content] Converted content or error message
48
- def convert(file_path)
49
- raise NotImplementedError, "#{self.class.name} must implement #convert"
50
- end
51
-
52
- protected
53
-
54
- # Return a system reminder about missing gem
55
- # @param format [String] Format name (e.g., "PDF")
56
- # @param gem_name [String] Required gem name
57
- # @return [String]
58
- def unsupported_format_reminder(format, gem_name)
59
- <<~REMINDER
60
- <system-reminder>
61
- This file is a #{format} document, but the required gem is not installed.
62
-
63
- To enable #{format} file reading, please install the gem:
64
- gem install #{gem_name}
65
-
66
- Or add to your Gemfile:
67
- gem "#{gem_name}"
68
-
69
- Don't install the gem yourself. Ask the user if they would like you to install this gem.
70
- </system-reminder>
71
- REMINDER
72
- end
73
-
74
- # Return an error message
75
- # @param message [String] Error message
76
- # @return [String]
77
- def error(message)
78
- "Error: #{message}"
79
- end
80
- end
81
- end
82
- end
83
- end
@@ -1,99 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- module DocumentConverters
6
- # Converts DOCX documents to text with image extraction
7
- class DocxConverter < BaseConverter
8
- class << self
9
- def gem_name
10
- "docx"
11
- end
12
-
13
- def format_name
14
- "DOCX"
15
- end
16
-
17
- def extensions
18
- [".docx", ".doc"]
19
- end
20
- end
21
-
22
- # Convert a DOCX document to text/content
23
- # @param file_path [String] Path to the DOCX file
24
- # @return [String, RubyLLM::Content] Converted content or error message
25
- def convert(file_path)
26
- unless self.class.available?
27
- return unsupported_format_reminder(self.class.format_name, self.class.gem_name)
28
- end
29
-
30
- # Check for legacy DOC format
31
- if File.extname(file_path).downcase == ".doc"
32
- return error("DOC format is not supported. Please convert to DOCX first.")
33
- end
34
-
35
- begin
36
- require "docx"
37
- require "tmpdir"
38
-
39
- doc = Docx::Document.open(file_path)
40
-
41
- # Extract images from the DOCX
42
- image_paths = ImageExtractors::DocxImageExtractor.extract_images(doc, file_path)
43
-
44
- output = []
45
- output << "Document: #{File.basename(file_path)}"
46
- output << "=" * 60
47
- output << ""
48
-
49
- # Extract paragraphs
50
- paragraphs = doc.paragraphs.map(&:text).reject(&:empty?)
51
-
52
- # Check for empty document
53
- if paragraphs.empty? && doc.tables.empty?
54
- output << "(Document is empty - no paragraphs or tables)"
55
- else
56
- output += paragraphs
57
-
58
- # Extract tables with enhanced formatting
59
- if doc.tables.any?
60
- output << ""
61
- output << "Tables:"
62
- output << "-" * 60
63
-
64
- doc.tables.each_with_index do |table, idx|
65
- output << ""
66
- output << "Table #{idx + 1} (#{table.row_count} rows × #{table.column_count} columns):"
67
-
68
- table.rows.each do |row|
69
- output << row.cells.map(&:text).join(" | ")
70
- end
71
- end
72
- end
73
- end
74
-
75
- text_content = output.join("\n")
76
-
77
- # If there are images, return Content with attachments
78
- if image_paths.any?
79
- content = RubyLLM::Content.new(text_content)
80
- image_paths.each do |image_path|
81
- content.add_attachment(image_path)
82
- end
83
- content
84
- else
85
- # No images, return just text
86
- text_content
87
- end
88
- rescue Zip::Error => e
89
- error("Invalid or corrupted DOCX file: #{e.message}")
90
- rescue Errno::ENOENT => e
91
- error("File not found or missing document.xml: #{e.message}")
92
- rescue StandardError => e
93
- error("Failed to parse DOCX file: #{e.message}")
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- module DocumentConverters
6
- # Converter for HTML to Markdown
7
- # Uses reverse_markdown gem if available, otherwise falls back to simple regex-based conversion
8
- class HtmlConverter < BaseConverter
9
- class << self
10
- def gem_name
11
- "reverse_markdown"
12
- end
13
-
14
- def format_name
15
- "HTML"
16
- end
17
-
18
- def extensions
19
- [".html", ".htm"]
20
- end
21
- end
22
-
23
- # Convert HTML string to Markdown
24
- # @param html [String] HTML content to convert
25
- # @return [String] Markdown content
26
- def convert_string(html)
27
- if self.class.available?
28
- convert_with_gem(html)
29
- else
30
- convert_simple(html)
31
- end
32
- end
33
-
34
- # Convert HTML file to Markdown
35
- # @param file_path [String] Path to HTML file
36
- # @return [String] Markdown content
37
- def convert(file_path)
38
- html = File.read(file_path)
39
- convert_string(html)
40
- rescue StandardError => e
41
- error("Failed to read HTML file: #{e.message}")
42
- end
43
-
44
- private
45
-
46
- # Convert HTML to Markdown using reverse_markdown gem
47
- # @param html [String] HTML content
48
- # @return [String] Markdown content
49
- def convert_with_gem(html)
50
- require "reverse_markdown"
51
-
52
- ReverseMarkdown.convert(html, unknown_tags: :bypass, github_flavored: true)
53
- rescue StandardError
54
- # Fallback to simple conversion if gem conversion fails
55
- convert_simple(html)
56
- end
57
-
58
- # Simple regex-based HTML to Markdown conversion (fallback)
59
- # @param html [String] HTML content
60
- # @return [String] Markdown content
61
- def convert_simple(html)
62
- # Remove script and style tags
63
- content = html.gsub(%r{<script[^>]*>.*?</script>}im, "")
64
- content = content.gsub(%r{<style[^>]*>.*?</style>}im, "")
65
-
66
- # Convert common HTML elements
67
- content = content.gsub(%r{<h1[^>]*>(.*?)</h1>}im, "\n# \\1\n")
68
- content = content.gsub(%r{<h2[^>]*>(.*?)</h2>}im, "\n## \\1\n")
69
- content = content.gsub(%r{<h3[^>]*>(.*?)</h3>}im, "\n### \\1\n")
70
- content = content.gsub(%r{<h4[^>]*>(.*?)</h4>}im, "\n#### \\1\n")
71
- content = content.gsub(%r{<h5[^>]*>(.*?)</h5>}im, "\n##### \\1\n")
72
- content = content.gsub(%r{<h6[^>]*>(.*?)</h6>}im, "\n###### \\1\n")
73
- content = content.gsub(%r{<p[^>]*>(.*?)</p>}im, "\n\\1\n")
74
- content = content.gsub(%r{<br\s*/?>}i, "\n")
75
- content = content.gsub(%r{<strong[^>]*>(.*?)</strong>}im, "**\\1**")
76
- content = content.gsub(%r{<b[^>]*>(.*?)</b>}im, "**\\1**")
77
- content = content.gsub(%r{<em[^>]*>(.*?)</em>}im, "_\\1_")
78
- content = content.gsub(%r{<i[^>]*>(.*?)</i>}im, "_\\1_")
79
- content = content.gsub(%r{<code[^>]*>(.*?)</code>}im, "`\\1`")
80
- content = content.gsub(%r{<a[^>]*href=["']([^"']*)["'][^>]*>(.*?)</a>}im, "[\\2](\\1)")
81
- content = content.gsub(%r{<li[^>]*>(.*?)</li>}im, "- \\1\n")
82
-
83
- # Remove remaining HTML tags
84
- content = content.gsub(/<[^>]+>/, "")
85
-
86
- # Decode HTML entities
87
- content = content.gsub("&lt;", "<")
88
- content = content.gsub("&gt;", ">")
89
- content = content.gsub("&amp;", "&")
90
- content = content.gsub("&quot;", "\"")
91
- content = content.gsub("&#39;", "'")
92
- content = content.gsub("&nbsp;", " ")
93
-
94
- # Clean up whitespace
95
- content = content.gsub(/\n\n\n+/, "\n\n")
96
- content.strip
97
- end
98
- end
99
- end
100
- end
101
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- module DocumentConverters
6
- # Converts PDF documents to text with image extraction
7
- class PdfConverter < BaseConverter
8
- class << self
9
- def gem_name
10
- "pdf-reader"
11
- end
12
-
13
- def format_name
14
- "PDF"
15
- end
16
-
17
- def extensions
18
- [".pdf"]
19
- end
20
- end
21
-
22
- # Convert a PDF document to text/content
23
- # @param file_path [String] Path to the PDF file
24
- # @return [String, RubyLLM::Content] Converted content or error message
25
- def convert(file_path)
26
- unless self.class.available?
27
- return unsupported_format_reminder(self.class.format_name, self.class.gem_name)
28
- end
29
-
30
- begin
31
- require "pdf-reader"
32
- require "tmpdir"
33
- require "fileutils"
34
-
35
- reader = PDF::Reader.new(file_path)
36
- output = []
37
- output << "PDF Document: #{File.basename(file_path)}"
38
- output << "=" * 60
39
- output << "Pages: #{reader.page_count}"
40
- output << ""
41
-
42
- # Extract images from the PDF
43
- image_paths = ImageExtractors::PdfImageExtractor.extract_images(reader, file_path)
44
-
45
- # Extract text from each page
46
- reader.pages.each_with_index do |page, index|
47
- output << "Page #{index + 1}:"
48
- output << "-" * 60
49
- text = page.text.strip
50
- output << (text.empty? ? "(No text content on this page)" : text)
51
- output << ""
52
- end
53
-
54
- text_content = output.join("\n")
55
-
56
- # If there are images, return Content with attachments
57
- if image_paths.any?
58
- content = RubyLLM::Content.new(text_content)
59
- image_paths.each do |image_path|
60
- content.add_attachment(image_path)
61
- end
62
- content
63
- else
64
- # No images, return just text
65
- text_content
66
- end
67
- rescue PDF::Reader::MalformedPDFError => e
68
- error("PDF file is malformed: #{e.message}")
69
- rescue PDF::Reader::UnsupportedFeatureError => e
70
- error("PDF contains unsupported features: #{e.message}")
71
- rescue StandardError => e
72
- error("Failed to parse PDF file: #{e.message}")
73
- end
74
- end
75
- end
76
- end
77
- end
78
- end