swarm_memory 2.1.4 → 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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. data/lib/swarm_memory.rb +7 -2
  4. metadata +6 -185
  5. data/lib/claude_swarm/base_executor.rb +0 -133
  6. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  7. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  8. data/lib/claude_swarm/cli.rb +0 -697
  9. data/lib/claude_swarm/commands/ps.rb +0 -215
  10. data/lib/claude_swarm/commands/show.rb +0 -139
  11. data/lib/claude_swarm/configuration.rb +0 -373
  12. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  13. data/lib/claude_swarm/json_handler.rb +0 -91
  14. data/lib/claude_swarm/mcp_generator.rb +0 -243
  15. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  16. data/lib/claude_swarm/openai/executor.rb +0 -256
  17. data/lib/claude_swarm/openai/responses.rb +0 -319
  18. data/lib/claude_swarm/orchestrator.rb +0 -878
  19. data/lib/claude_swarm/process_tracker.rb +0 -78
  20. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  21. data/lib/claude_swarm/session_path.rb +0 -42
  22. data/lib/claude_swarm/settings_generator.rb +0 -77
  23. data/lib/claude_swarm/system_utils.rb +0 -46
  24. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  25. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  27. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  28. data/lib/claude_swarm/version.rb +0 -5
  29. data/lib/claude_swarm/worktree_manager.rb +0 -475
  30. data/lib/claude_swarm/yaml_loader.rb +0 -22
  31. data/lib/claude_swarm.rb +0 -67
  32. data/lib/swarm_cli/cli.rb +0 -201
  33. data/lib/swarm_cli/command_registry.rb +0 -61
  34. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  35. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  36. data/lib/swarm_cli/commands/migrate.rb +0 -55
  37. data/lib/swarm_cli/commands/run.rb +0 -173
  38. data/lib/swarm_cli/config_loader.rb +0 -98
  39. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  40. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  41. data/lib/swarm_cli/interactive_repl.rb +0 -924
  42. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  43. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  44. data/lib/swarm_cli/migrate_options.rb +0 -54
  45. data/lib/swarm_cli/migrator.rb +0 -132
  46. data/lib/swarm_cli/options.rb +0 -151
  47. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  48. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  49. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  50. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  51. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  52. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  53. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  54. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  55. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  56. data/lib/swarm_cli/ui/icons.rb +0 -36
  57. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  58. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  59. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  60. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  61. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  62. data/lib/swarm_cli/version.rb +0 -5
  63. data/lib/swarm_cli.rb +0 -46
  64. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  65. data/lib/swarm_sdk/agent/builder.rb +0 -552
  66. data/lib/swarm_sdk/agent/chat.rb +0 -774
  67. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  68. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  69. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  70. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  71. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  72. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  73. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  75. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  76. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  77. data/lib/swarm_sdk/agent/context.rb +0 -116
  78. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  79. data/lib/swarm_sdk/agent/definition.rb +0 -477
  80. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  81. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  82. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  83. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  84. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  85. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  86. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  87. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  88. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  89. data/lib/swarm_sdk/configuration.rb +0 -135
  90. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  91. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  92. data/lib/swarm_sdk/context_compactor.rb +0 -335
  93. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  94. data/lib/swarm_sdk/context_management/context.rb +0 -328
  95. data/lib/swarm_sdk/defaults.rb +0 -196
  96. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  97. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  98. data/lib/swarm_sdk/hooks/context.rb +0 -197
  99. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  100. data/lib/swarm_sdk/hooks/error.rb +0 -29
  101. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  102. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  103. data/lib/swarm_sdk/hooks/result.rb +0 -150
  104. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  105. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  106. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  107. data/lib/swarm_sdk/log_collector.rb +0 -227
  108. data/lib/swarm_sdk/log_stream.rb +0 -127
  109. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  110. data/lib/swarm_sdk/model_aliases.json +0 -8
  111. data/lib/swarm_sdk/models.json +0 -1
  112. data/lib/swarm_sdk/models.rb +0 -120
  113. data/lib/swarm_sdk/node_context.rb +0 -245
  114. data/lib/swarm_sdk/observer/builder.rb +0 -81
  115. data/lib/swarm_sdk/observer/config.rb +0 -45
  116. data/lib/swarm_sdk/observer/manager.rb +0 -236
  117. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  118. data/lib/swarm_sdk/permissions/config.rb +0 -239
  119. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  120. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  121. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  122. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  123. data/lib/swarm_sdk/plugin.rb +0 -309
  124. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  125. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  126. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  127. data/lib/swarm_sdk/restore_result.rb +0 -65
  128. data/lib/swarm_sdk/result.rb +0 -123
  129. data/lib/swarm_sdk/snapshot.rb +0 -156
  130. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  131. data/lib/swarm_sdk/state_restorer.rb +0 -476
  132. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  133. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  134. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  135. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  136. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  137. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  138. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  139. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  140. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  141. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  142. data/lib/swarm_sdk/swarm.rb +0 -717
  143. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  144. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  145. data/lib/swarm_sdk/tools/bash.rb +0 -282
  146. data/lib/swarm_sdk/tools/clock.rb +0 -44
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  160. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  161. data/lib/swarm_sdk/tools/read.rb +0 -261
  162. data/lib/swarm_sdk/tools/registry.rb +0 -205
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  166. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  167. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  168. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  169. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  170. data/lib/swarm_sdk/tools/think.rb +0 -98
  171. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  172. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  173. data/lib/swarm_sdk/tools/write.rb +0 -112
  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 -79
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  182. data/lib/swarm_sdk/workflow.rb +0 -554
  183. data/lib/swarm_sdk.rb +0 -524
  184. /data/lib/swarm_memory/{errors.rb → error.rb} +0 -0
@@ -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