swarm_sdk 2.7.14 → 3.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +16 -0
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +4 -1
  4. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  5. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  6. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  7. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  8. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  9. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  10. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  11. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  12. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  13. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  14. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  15. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  16. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  17. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  18. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  19. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  20. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  24. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  25. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  26. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  27. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  28. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  29. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  30. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  31. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  32. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  33. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  34. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  35. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  36. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  37. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  38. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  39. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  40. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  41. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  42. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  43. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  44. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  45. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  46. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  47. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  48. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  49. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  50. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  51. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  52. data/lib/swarm_sdk/v3.rb +145 -0
  53. metadata +83 -148
  54. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  55. data/lib/swarm_sdk/agent/builder.rb +0 -705
  56. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  57. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  58. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  59. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  60. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  61. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  62. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  63. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  64. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  65. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  66. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  67. data/lib/swarm_sdk/agent/context.rb +0 -115
  68. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  69. data/lib/swarm_sdk/agent/definition.rb +0 -588
  70. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  71. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  72. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  73. data/lib/swarm_sdk/agent_registry.rb +0 -146
  74. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  75. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  76. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  77. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  78. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  79. data/lib/swarm_sdk/config.rb +0 -368
  80. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  81. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  82. data/lib/swarm_sdk/configuration.rb +0 -165
  83. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  84. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  85. data/lib/swarm_sdk/context_compactor.rb +0 -335
  86. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  87. data/lib/swarm_sdk/context_management/context.rb +0 -328
  88. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  89. data/lib/swarm_sdk/defaults.rb +0 -251
  90. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  91. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  92. data/lib/swarm_sdk/hooks/context.rb +0 -197
  93. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  94. data/lib/swarm_sdk/hooks/error.rb +0 -29
  95. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  96. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  97. data/lib/swarm_sdk/hooks/result.rb +0 -150
  98. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  99. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  100. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  101. data/lib/swarm_sdk/log_collector.rb +0 -227
  102. data/lib/swarm_sdk/log_stream.rb +0 -127
  103. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  104. data/lib/swarm_sdk/model_aliases.json +0 -8
  105. data/lib/swarm_sdk/models.json +0 -44002
  106. data/lib/swarm_sdk/models.rb +0 -161
  107. data/lib/swarm_sdk/node_context.rb +0 -245
  108. data/lib/swarm_sdk/observer/builder.rb +0 -81
  109. data/lib/swarm_sdk/observer/config.rb +0 -45
  110. data/lib/swarm_sdk/observer/manager.rb +0 -248
  111. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  112. data/lib/swarm_sdk/permissions/config.rb +0 -239
  113. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  114. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  115. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  116. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  117. data/lib/swarm_sdk/plugin.rb +0 -309
  118. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  119. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  120. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  121. data/lib/swarm_sdk/restore_result.rb +0 -65
  122. data/lib/swarm_sdk/result.rb +0 -241
  123. data/lib/swarm_sdk/snapshot.rb +0 -156
  124. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  125. data/lib/swarm_sdk/state_restorer.rb +0 -476
  126. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  127. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  128. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  129. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  130. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  131. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  132. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  133. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  134. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  135. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  136. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  137. data/lib/swarm_sdk/swarm.rb +0 -973
  138. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  139. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  140. data/lib/swarm_sdk/tools/base.rb +0 -63
  141. data/lib/swarm_sdk/tools/bash.rb +0 -280
  142. data/lib/swarm_sdk/tools/clock.rb +0 -46
  143. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  144. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  145. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  146. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  147. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  148. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  149. data/lib/swarm_sdk/tools/edit.rb +0 -145
  150. data/lib/swarm_sdk/tools/glob.rb +0 -166
  151. data/lib/swarm_sdk/tools/grep.rb +0 -235
  152. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  153. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  154. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  155. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  156. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  157. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  158. data/lib/swarm_sdk/tools/read.rb +0 -261
  159. data/lib/swarm_sdk/tools/registry.rb +0 -205
  160. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  161. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  163. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  164. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  165. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  166. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  167. data/lib/swarm_sdk/tools/think.rb +0 -100
  168. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  169. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  170. data/lib/swarm_sdk/tools/write.rb +0 -112
  171. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  172. data/lib/swarm_sdk/utils.rb +0 -68
  173. data/lib/swarm_sdk/validation_result.rb +0 -33
  174. data/lib/swarm_sdk/version.rb +0 -5
  175. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  176. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  177. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  178. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  179. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  180. data/lib/swarm_sdk/workflow.rb +0 -589
  181. data/lib/swarm_sdk.rb +0 -721
@@ -1,100 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- # Think tool for explicit reasoning and planning
6
- #
7
- # Allows the agent to write down thoughts, plans, strategies, and intermediate
8
- # calculations. These thoughts become part of the conversation context, enabling
9
- # better attention and reasoning through complex problems.
10
- #
11
- # This is inspired by research showing that explicitly articulating reasoning steps
12
- # (chain-of-thought prompting) leads to significantly better outcomes, especially
13
- # for complex tasks requiring multi-step reasoning or arithmetic.
14
- class Think < Base
15
- removable false # Think is always available
16
-
17
- def name
18
- "Think"
19
- end
20
-
21
- description <<~DESC
22
- **IMPORTANT: You SHOULD use this tool frequently throughout your work. Using this tool leads to significantly
23
- better outcomes and more accurate solutions. Make it a habit to think before acting.**
24
-
25
- This tool allows you to write down your thoughts, plans, strategies, and intermediate calculations.
26
- Think of it as your working memory - just as humans think before speaking or acting, you should think before
27
- using other tools or providing responses.
28
-
29
- **STRONGLY RECOMMENDED to use this tool:**
30
- - **ALWAYS** before starting any task (even simple ones)
31
- - **ALWAYS** when you need to do any arithmetic or counting
32
- - **ALWAYS** after reading files or getting search results to process what you learned
33
- - **FREQUENTLY** between steps to track progress and plan next actions
34
-
35
- This is your private thinking space - use it liberally to enhance your problem-solving capabilities. Recording
36
- your thoughts helps you maintain context across multiple steps and remember important information throughout your task.
37
-
38
- When and how to use this tool:
39
-
40
- 1. **Before starting any complex task**: Write down your understanding of the problem, break it into smaller
41
- sub-tasks, and create a step-by-step plan. Example:
42
- - "The user wants me to refactor this codebase. Let me first understand the structure..."
43
- - "I need to: 1) Analyze current architecture, 2) Identify pain points, 3) Propose changes..."
44
-
45
- 2. **For arithmetic and calculations**: Work through math problems step by step. Example:
46
- - "If we have 150 requests/second and each takes 20ms, that's 150 * 0.02 = 3 seconds of CPU time..."
47
- - "Converting 2GB to bytes: 2 * 1024 * 1024 * 1024 = 2,147,483,648 bytes"
48
-
49
- 3. **After completing sub-tasks**: Summarize what you've accomplished and what remains. Example:
50
- - "I've successfully implemented the authentication module. Next, I need to integrate it with the API..."
51
- - "Fixed 3 out of 5 bugs. Remaining: memory leak in parser, race condition in worker thread"
52
-
53
- 4. **When encountering complexity**: Break down complex logic or decisions. Example:
54
- - "This function has multiple edge cases. Let me list them: null input, empty array, negative numbers..."
55
- - "The user's request is ambiguous. Possible interpretations: A) modify existing code, B) create new module..."
56
-
57
- 5. **For remembering context**: Store important information you'll need later. Example:
58
- - "Important: The user mentioned they're using Ruby 3.2, so I can use pattern matching"
59
- - "File structure: main.rb requires from lib/, config is in config.yml"
60
-
61
- 6. **When debugging or analyzing**: Track your investigation process. Example:
62
- - "The error occurs in line 42. Let me trace backwards: function called from main(), receives data from..."
63
- - "Hypothesis: the bug might be due to timezone differences. Let me check..."
64
-
65
- 7. **For creative problem-solving**: Brainstorm multiple approaches before choosing one. Example:
66
- - "Approaches to optimize this: 1) Add caching, 2) Use parallel processing, 3) Optimize algorithm..."
67
- - "Design patterns that could work here: Factory, Observer, or maybe Strategy pattern..."
68
-
69
- **Remember: The most successful agents use this tool 5-10 times per task on average. If you haven't used this
70
- tool in the last 2-3 actions, you probably should. Using this tool is a sign of thoughtful, methodical problem
71
- solving and leads to fewer mistakes and better solutions.**
72
-
73
- Your thoughts persist throughout your session as part of the conversation history, so you can refer
74
- back to earlier thinking. Use clear formatting and organization to make it easy to reference
75
- later. Don't hesitate to think out loud - this tool is designed to augment your cognitive capabilities and help
76
- you deliver better solutions.
77
-
78
- **CRITICAL:** The Think tool takes only one parameter: thoughts. Do not include any other parameters.
79
- DESC
80
-
81
- param :thoughts,
82
- type: "string",
83
- desc: "Your thoughts, plans, calculations, or any notes you want to record",
84
- required: true
85
-
86
- def execute(**kwargs)
87
- <<~RESP
88
- Thought noted.
89
- RESP
90
- # <system-reminder>The user cannot see your thoughts. You MUST NOT stop without giving the user a response.</system-reminder>
91
- end
92
-
93
- private
94
-
95
- def validation_error(message)
96
- "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
97
- end
98
- end
99
- end
100
- end
@@ -1,237 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- # TodoWrite tool for creating and managing structured task lists
6
- #
7
- # This tool helps agents track progress on complex multi-step tasks.
8
- # Each agent maintains its own independent todo list.
9
- class TodoWrite < Base
10
- removable false # TodoWrite is always available
11
-
12
- # Factory pattern: declare what parameters this tool needs for instantiation
13
- class << self
14
- def creation_requirements
15
- [:agent_name]
16
- end
17
- end
18
-
19
- description <<~DESC
20
- Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
21
- It also helps the user understand the progress of the task and overall progress of their requests.
22
-
23
- ## When to Use This Tool
24
- Use this tool proactively in these scenarios:
25
-
26
- **CRITICAL**: Follow this workflow for multi-step tasks:
27
- 1. FIRST: Analyze the task scope (gather information, understand requirements)
28
- 2. SECOND: Create a COMPLETE todo list with ALL known tasks BEFORE starting work
29
- 3. THIRD: Execute tasks, marking in_progress → completed as you work
30
- 4. ONLY add new todos if unexpected work is discovered during execution
31
-
32
- Use the todo list when:
33
- 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
34
- 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
35
- 3. User explicitly requests todo list - When the user directly asks you to use the todo list
36
- 4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
37
- 5. After receiving new instructions - After analyzing scope, create complete todo list before starting work
38
- 6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time
39
- 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during execution
40
-
41
- ## When NOT to Use This Tool
42
-
43
- Skip using this tool when:
44
- 1. There is only a single, straightforward task
45
- 2. The task is trivial and tracking it provides no organizational benefit
46
- 3. The task can be completed in less than 3 trivial steps
47
- 4. The task is purely conversational or informational
48
-
49
- NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.
50
-
51
- ## Task States and Management
52
-
53
- 1. **Task States**: Use these states to track progress:
54
- - pending: Task not yet started
55
- - in_progress: Currently working on (limit to ONE task at a time)
56
- - completed: Task finished successfully
57
-
58
- **IMPORTANT**: Task descriptions must have two forms:
59
- - content: The imperative form describing what needs to be done (e.g., "Run tests", "Build the project")
60
- - activeForm: The present continuous form shown during execution (e.g., "Running tests", "Building the project")
61
-
62
- 2. **Task Management**:
63
- - Update task status in real-time as you work
64
- - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
65
- - Exactly ONE task must be in_progress at any time (not less, not more)
66
- - Complete current tasks before starting new ones
67
- - Remove tasks that are no longer relevant from the list entirely
68
- - **CRITICAL**: You MUST complete ALL pending todos before giving your final answer to the user
69
- - NEVER leave in_progress or pending tasks when you finish responding
70
-
71
- 3. **Task Completion Requirements**:
72
- - ONLY mark a task as completed when you have FULLY accomplished it
73
- - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
74
- - When blocked, create a new task describing what needs to be resolved
75
- - Never mark a task as completed if:
76
- - Tests are failing
77
- - Implementation is partial
78
- - You encountered unresolved errors
79
- - You couldn't find necessary files or dependencies
80
-
81
- 4. **Task Breakdown**:
82
- - Create specific, actionable items
83
- - Break complex tasks into smaller, manageable steps
84
- - Use clear, descriptive task names
85
- - Always provide both forms (content and activeForm)
86
-
87
- ## Examples
88
-
89
- **Coding Tasks**:
90
- - content: "Fix authentication bug in login handler"
91
- - activeForm: "Fixing authentication bug in login handler"
92
-
93
- **Non-Coding Tasks**:
94
- - content: "Analyze customer feedback from Q4 survey"
95
- - activeForm: "Analyzing customer feedback from Q4 survey"
96
-
97
- **Research Tasks**:
98
- - content: "Research best practices for API rate limiting"
99
- - activeForm: "Researching best practices for API rate limiting"
100
-
101
- When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.
102
- DESC
103
-
104
- param :todos_json,
105
- type: "string",
106
- desc: <<~DESC.chomp,
107
- JSON array of todo objects. Each todo must have:
108
- content (string, task in imperative form like 'Run tests'),
109
- status (string, one of: 'pending', 'in_progress', 'completed'),
110
- activeForm (string, task in present continuous form like 'Running tests').
111
- Example: [{"content":"Read file","status":"pending","activeForm":"Reading file"}]
112
- DESC
113
- required: true
114
-
115
- # Initialize the TodoWrite tool for a specific agent
116
- #
117
- # @param agent_name [Symbol, String] The agent identifier
118
- def initialize(agent_name:)
119
- super()
120
- @agent_name = agent_name.to_sym
121
- end
122
-
123
- # Override name to return simple "TodoWrite" instead of full class path
124
- def name
125
- "TodoWrite"
126
- end
127
-
128
- def execute(todos_json:)
129
- # Parse JSON
130
- todos = begin
131
- JSON.parse(todos_json)
132
- rescue JSON::ParserError
133
- nil
134
- end
135
-
136
- return validation_error("Invalid JSON format. Please provide a valid JSON array of todo objects.") if todos.nil?
137
-
138
- # Validate todos structure
139
- unless todos.is_a?(Array)
140
- return validation_error("todos must be an array of todo objects")
141
- end
142
-
143
- if todos.empty?
144
- return validation_error("todos array cannot be empty")
145
- end
146
-
147
- validated_todos = []
148
- errors = []
149
-
150
- todos.each_with_index do |todo, index|
151
- unless todo.is_a?(Hash)
152
- errors << "Todo at index #{index} must be a hash/object"
153
- next
154
- end
155
-
156
- # Convert string keys to symbols for consistency
157
- todo = todo.transform_keys(&:to_sym) if todo.is_a?(Hash)
158
-
159
- # Validate required fields
160
- unless todo[:content]
161
- errors << "Todo at index #{index} missing required field 'content'"
162
- next
163
- end
164
-
165
- unless todo[:status]
166
- errors << "Todo at index #{index} missing required field 'status'"
167
- next
168
- end
169
-
170
- unless todo[:activeForm]
171
- errors << "Todo at index #{index} missing required field 'activeForm'"
172
- next
173
- end
174
-
175
- # Validate status values
176
- valid_statuses = ["pending", "in_progress", "completed"]
177
- unless valid_statuses.include?(todo[:status].to_s)
178
- errors << "Todo at index #{index} has invalid status '#{todo[:status]}'. Must be one of: #{valid_statuses.join(", ")}"
179
- next
180
- end
181
-
182
- # Validate content and activeForm are non-empty
183
- if todo[:content].to_s.strip.empty?
184
- errors << "Todo at index #{index} has empty content"
185
- next
186
- end
187
-
188
- if todo[:activeForm].to_s.strip.empty?
189
- errors << "Todo at index #{index} has empty activeForm"
190
- next
191
- end
192
-
193
- validated_todos << {
194
- content: todo[:content].to_s,
195
- status: todo[:status].to_s,
196
- activeForm: todo[:activeForm].to_s,
197
- }
198
- end
199
-
200
- return validation_error("TodoWrite failed due to the following issues:\n#{errors.join("\n")}") unless errors.empty?
201
-
202
- # Check that exactly one task is in_progress (with helpful message)
203
- in_progress_count = validated_todos.count { |t| t[:status] == "in_progress" }
204
- warning_message = if in_progress_count == 0
205
- "Warning: No tasks marked as in_progress. You should have exactly ONE task in_progress at a time.\n" \
206
- "Please mark the task you're currently working on as in_progress.\n\n"
207
- elsif in_progress_count > 1
208
- "Warning: Multiple tasks marked as in_progress (#{in_progress_count} tasks).\n" \
209
- "You should have exactly ONE task in_progress at a time.\n" \
210
- "Please ensure only the current task is in_progress, others should be pending or completed.\n\n"
211
- else
212
- ""
213
- end
214
-
215
- # Store the validated todos
216
- Stores::TodoManager.set_todos(@agent_name, validated_todos)
217
-
218
- <<~RESPONSE
219
- <system-reminder>
220
- #{warning_message}Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:
221
- #{validated_todos.map { |t| "- #{t[:content]} (#{t[:status]})" }.join("\n")}
222
- Keep going with the tasks at hand if applicable.
223
- </system-reminder>
224
- RESPONSE
225
- rescue StandardError => e
226
- "Error managing todos: #{e.class.name} - #{e.message}"
227
- end
228
-
229
- private
230
-
231
- # Helper method for validation errors
232
- def validation_error(message)
233
- "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
234
- end
235
- end
236
- end
237
- end
@@ -1,264 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Tools
5
- # WebFetch tool for fetching and processing web content
6
- #
7
- # Fetches content from URLs, converts HTML to markdown, and processes it
8
- # using an AI model to extract information based on a provided prompt.
9
- class WebFetch < Base
10
- def initialize
11
- super()
12
- @cache = {}
13
- @cache_ttl = 900 # 15 minutes in seconds
14
- end
15
-
16
- def name
17
- "WebFetch"
18
- end
19
-
20
- description <<~DESC
21
- - Fetches content from a specified URL and converts it to markdown
22
- - Optionally processes the content with an LLM if configured
23
- - Fetches the URL content, converts HTML to markdown
24
- - Returns markdown content or LLM analysis (based on configuration)
25
- - Use this tool when you need to retrieve and analyze web content
26
-
27
- Usage notes:
28
- - IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions. All MCP-provided tools start with "mcp__".
29
- - The URL must be a fully-formed valid URL
30
- - HTTP URLs will be automatically upgraded to HTTPS
31
- - This tool is read-only and does not modify any files
32
- - Content will be truncated if very large
33
- - Includes a self-cleaning 15-minute cache for faster responses
34
- - When a URL redirects to a different host, the tool will inform you and provide the redirect URL in a special format. You should then make a new WebFetch request with the redirect URL to fetch the content.
35
-
36
- LLM Processing:
37
- - When SwarmSDK is configured with webfetch_provider and webfetch_model, the 'prompt' parameter is required
38
- - The tool will process the markdown content with the configured LLM using your prompt
39
- - Without this configuration, the tool returns raw markdown and the 'prompt' parameter is optional (ignored if provided)
40
- - Configure with: SwarmSDK.configure { |c| c.webfetch_provider = "anthropic"; c.webfetch_model = "claude-3-5-haiku-20241022" }
41
- DESC
42
-
43
- param :url,
44
- type: "string",
45
- desc: "The URL to fetch content from",
46
- required: true
47
-
48
- param :prompt,
49
- type: "string",
50
- desc: "The prompt to run on the fetched content. Required when SwarmSDK is configured with webfetch_provider and webfetch_model. Optional otherwise (ignored if LLM processing not configured).",
51
- required: false
52
-
53
- # NOTE: Content length and timeout now accessed via SwarmSDK.config
54
- USER_AGENT = "SwarmSDK WebFetch Tool (https://github.com/parruda/claude-swarm)"
55
-
56
- def execute(url:, prompt: nil)
57
- # Validate inputs
58
- return validation_error("url is required") if url.nil? || url.empty?
59
-
60
- # Check if LLM processing is enabled (lazy check)
61
- llm_enabled = SwarmSDK.config.webfetch_llm_enabled?
62
-
63
- # Validate prompt when LLM processing is enabled
64
- if llm_enabled && (prompt.nil? || prompt.empty?)
65
- return validation_error("prompt is required when LLM processing is configured")
66
- end
67
-
68
- # Validate and normalize URL
69
- normalized_url = normalize_url(url)
70
- return validation_error("Invalid URL format: #{url}") unless normalized_url
71
-
72
- # Check cache first (cache key includes prompt if LLM is enabled)
73
- cache_key = llm_enabled ? "#{normalized_url}:#{prompt}" : normalized_url
74
- cached = get_from_cache(cache_key)
75
- return cached if cached
76
-
77
- # Fetch the URL
78
- fetch_result = fetch_url(normalized_url)
79
- return fetch_result if fetch_result.is_a?(String) && fetch_result.start_with?("Error")
80
-
81
- # Check for redirects to different hosts
82
- if fetch_result[:redirect_url] && different_host?(normalized_url, fetch_result[:redirect_url])
83
- return format_redirect_message(fetch_result[:redirect_url])
84
- end
85
-
86
- # Convert HTML to markdown
87
- markdown_content = html_to_markdown(fetch_result[:body])
88
-
89
- # Truncate if too long
90
- max_content = SwarmSDK.config.web_fetch_character_limit
91
- if markdown_content.length > max_content
92
- markdown_content = markdown_content[0...max_content]
93
- markdown_content += "\n\n[Content truncated due to length]"
94
- end
95
-
96
- # Process with AI model if LLM is enabled, otherwise return markdown
97
- result = if llm_enabled
98
- process_with_llm(markdown_content, prompt, normalized_url)
99
- else
100
- markdown_content
101
- end
102
-
103
- # Cache the result
104
- store_in_cache(cache_key, result)
105
-
106
- result
107
- rescue StandardError => e
108
- error("Unexpected error fetching URL: #{e.class.name} - #{e.message}")
109
- end
110
-
111
- private
112
-
113
- def validation_error(message)
114
- "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
115
- end
116
-
117
- def error(message)
118
- "Error: #{message}"
119
- end
120
-
121
- def normalize_url(url)
122
- # Upgrade HTTP to HTTPS
123
- url = url.sub(%r{^http://}, "https://")
124
-
125
- # Validate URL format
126
- uri = URI.parse(url)
127
- return unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
128
- return unless uri.host
129
-
130
- uri.to_s
131
- rescue URI::InvalidURIError
132
- nil
133
- end
134
-
135
- def different_host?(url1, url2)
136
- uri1 = URI.parse(url1)
137
- uri2 = URI.parse(url2)
138
- uri1.host != uri2.host
139
- rescue URI::InvalidURIError
140
- false
141
- end
142
-
143
- def fetch_url(url)
144
- require "faraday"
145
- require "faraday/follow_redirects"
146
-
147
- timeout = SwarmSDK.config.web_fetch_timeout
148
- response = Faraday.new(url: url) do |conn|
149
- conn.request(:url_encoded)
150
- conn.response(:follow_redirects, limit: 5)
151
- conn.adapter(Faraday.default_adapter)
152
- conn.options.timeout = timeout
153
- conn.options.open_timeout = timeout
154
- end.get do |req|
155
- req.headers["User-Agent"] = USER_AGENT
156
- req.headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
157
- end
158
-
159
- unless response.success?
160
- return error("HTTP #{response.status}: Failed to fetch URL")
161
- end
162
-
163
- # Check final URL for redirects
164
- final_url = response.env.url.to_s
165
- redirect_url = final_url if final_url != url
166
-
167
- {
168
- body: response.body,
169
- redirect_url: redirect_url,
170
- }
171
- rescue Faraday::TimeoutError
172
- error("Request timed out after #{SwarmSDK.config.web_fetch_timeout} seconds")
173
- rescue Faraday::ConnectionFailed => e
174
- error("Connection failed: #{e.message}")
175
- rescue StandardError => e
176
- error("Failed to fetch URL: #{e.class.name} - #{e.message}")
177
- end
178
-
179
- def html_to_markdown(html)
180
- # Use HtmlConverter to handle conversion with optional reverse_markdown gem
181
- converter = DocumentConverters::HtmlConverter.new
182
- converter.convert_string(html)
183
- end
184
-
185
- def process_with_llm(content, prompt, url)
186
- # Use configured model for processing
187
- # Format the prompt to include the content
188
- full_prompt = <<~PROMPT
189
- You are analyzing content from the URL: #{url}
190
-
191
- User request: #{prompt}
192
-
193
- Content:
194
- #{content}
195
-
196
- Please respond to the user's request based on the content above.
197
- PROMPT
198
-
199
- # Get config
200
- sdk_config = SwarmSDK.config
201
-
202
- # Build chat with configured provider and model
203
- chat_params = {
204
- model: sdk_config.webfetch_model,
205
- provider: sdk_config.webfetch_provider.to_sym,
206
- }
207
- chat_params[:base_url] = sdk_config.webfetch_base_url if sdk_config.webfetch_base_url
208
-
209
- chat = RubyLLM.chat(**chat_params).with_params(max_tokens: sdk_config.webfetch_max_tokens)
210
-
211
- response = chat.ask(full_prompt)
212
-
213
- # Extract the text response
214
- response_text = response.content
215
- return error("Failed to process content with LLM: No response text") unless response_text
216
-
217
- response_text
218
- rescue StandardError => e
219
- error("Failed to process content with LLM: #{e.class.name} - #{e.message}")
220
- end
221
-
222
- def format_redirect_message(redirect_url)
223
- <<~MESSAGE
224
- This URL redirected to a different host.
225
-
226
- Redirect URL: #{redirect_url}
227
-
228
- <system-reminder>
229
- The requested URL redirected to a different host. To fetch the content from the redirect URL,
230
- make a new WebFetch request with the redirect URL provided above.
231
- </system-reminder>
232
- MESSAGE
233
- end
234
-
235
- def get_from_cache(key)
236
- entry = @cache[key]
237
- return unless entry
238
-
239
- # Check if cache entry is still valid
240
- if Time.now.to_i - entry[:timestamp] > @cache_ttl
241
- @cache.delete(key)
242
- return
243
- end
244
-
245
- entry[:value]
246
- end
247
-
248
- def store_in_cache(key, value)
249
- # Clean old cache entries
250
- clean_cache
251
-
252
- @cache[key] = {
253
- value: value,
254
- timestamp: Time.now.to_i,
255
- }
256
- end
257
-
258
- def clean_cache
259
- now = Time.now.to_i
260
- @cache.delete_if { |_key, entry| now - entry[:timestamp] > @cache_ttl }
261
- end
262
- end
263
- end
264
- end