claude_swarm 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/commands/release.md +1 -1
- data/.claude/hooks/lint-code-files.rb +65 -0
- data/.rubocop.yml +22 -2
- data/CHANGELOG.md +14 -1
- data/CLAUDE.md +1 -1
- data/CONTRIBUTING.md +69 -0
- data/README.md +27 -2
- data/Rakefile +71 -3
- data/analyze_coverage.rb +94 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
- data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
- data/docs/v2/README.md +308 -0
- data/docs/v2/guides/claude-code-agents.md +262 -0
- data/docs/v2/guides/complete-tutorial.md +3088 -0
- data/docs/v2/guides/getting-started.md +1456 -0
- data/docs/v2/guides/memory-adapters.md +998 -0
- data/docs/v2/guides/plugins.md +816 -0
- data/docs/v2/guides/quick-start-cli.md +1745 -0
- data/docs/v2/guides/rails-integration.md +1902 -0
- data/docs/v2/guides/swarm-memory.md +599 -0
- data/docs/v2/reference/cli.md +729 -0
- data/docs/v2/reference/ruby-dsl.md +2154 -0
- data/docs/v2/reference/yaml.md +1835 -0
- data/docs-team-swarm.yml +2222 -0
- data/examples/learning-assistant/assistant.md +7 -0
- data/examples/learning-assistant/example-memories/concept-example.md +90 -0
- data/examples/learning-assistant/example-memories/experience-example.md +66 -0
- data/examples/learning-assistant/example-memories/fact-example.md +76 -0
- data/examples/learning-assistant/example-memories/memory-index.md +78 -0
- data/examples/learning-assistant/example-memories/skill-example.md +168 -0
- data/examples/learning-assistant/learning_assistant.rb +34 -0
- data/examples/learning-assistant/learning_assistant.yml +20 -0
- data/examples/v2/dsl/01_basic.rb +44 -0
- data/examples/v2/dsl/02_core_parameters.rb +59 -0
- data/examples/v2/dsl/03_capabilities.rb +71 -0
- data/examples/v2/dsl/04_llm_parameters.rb +56 -0
- data/examples/v2/dsl/05_advanced_flags.rb +73 -0
- data/examples/v2/dsl/06_permissions.rb +80 -0
- data/examples/v2/dsl/07_mcp_server.rb +62 -0
- data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
- data/examples/v2/dsl/09_agent_hooks.rb +67 -0
- data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
- data/examples/v2/dsl/11_delegation.rb +60 -0
- data/examples/v2/dsl/12_complete_integration.rb +137 -0
- data/examples/v2/file_tools_swarm.yml +102 -0
- data/examples/v2/hooks/01_basic_hooks.rb +133 -0
- data/examples/v2/hooks/02_usage_tracking.rb +201 -0
- data/examples/v2/hooks/03_production_monitoring.rb +429 -0
- data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
- data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
- data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
- data/examples/v2/hooks/swarm_summary.sh +44 -0
- data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
- data/examples/v2/hooks/validate_bash.rb +59 -0
- data/examples/v2/multi_directory_permissions.yml +221 -0
- data/examples/v2/node_context_demo.rb +127 -0
- data/examples/v2/node_workflow.rb +173 -0
- data/examples/v2/path_resolution_demo.rb +216 -0
- data/examples/v2/simple-swarm-v2.rb +90 -0
- data/examples/v2/simple-swarm-v2.yml +62 -0
- data/examples/v2/swarm.yml +71 -0
- data/examples/v2/swarm_with_hooks.yml +61 -0
- data/examples/v2/swarm_with_hooks_simple.yml +25 -0
- data/examples/v2/think_tool_demo.rb +62 -0
- data/exe/swarm +6 -0
- data/lib/claude_swarm/claude_mcp_server.rb +0 -6
- data/lib/claude_swarm/cli.rb +10 -3
- data/lib/claude_swarm/commands/ps.rb +19 -20
- data/lib/claude_swarm/commands/show.rb +1 -1
- data/lib/claude_swarm/configuration.rb +10 -12
- data/lib/claude_swarm/mcp_generator.rb +10 -1
- data/lib/claude_swarm/orchestrator.rb +73 -49
- data/lib/claude_swarm/system_utils.rb +37 -11
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm/worktree_manager.rb +1 -0
- data/lib/claude_swarm/yaml_loader.rb +22 -0
- data/lib/claude_swarm.rb +6 -2
- data/lib/swarm_cli/cli.rb +201 -0
- data/lib/swarm_cli/command_registry.rb +61 -0
- data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
- data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
- data/lib/swarm_cli/commands/migrate.rb +55 -0
- data/lib/swarm_cli/commands/run.rb +173 -0
- data/lib/swarm_cli/config_loader.rb +97 -0
- data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
- data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
- data/lib/swarm_cli/interactive_repl.rb +918 -0
- data/lib/swarm_cli/mcp_serve_options.rb +44 -0
- data/lib/swarm_cli/mcp_tools_options.rb +59 -0
- data/lib/swarm_cli/migrate_options.rb +54 -0
- data/lib/swarm_cli/migrator.rb +132 -0
- data/lib/swarm_cli/options.rb +151 -0
- data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
- data/lib/swarm_cli/ui/components/content_block.rb +120 -0
- data/lib/swarm_cli/ui/components/divider.rb +57 -0
- data/lib/swarm_cli/ui/components/panel.rb +62 -0
- data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
- data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
- data/lib/swarm_cli/ui/formatters/number.rb +58 -0
- data/lib/swarm_cli/ui/formatters/text.rb +77 -0
- data/lib/swarm_cli/ui/formatters/time.rb +73 -0
- data/lib/swarm_cli/ui/icons.rb +59 -0
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
- data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
- data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
- data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
- data/lib/swarm_cli/version.rb +5 -0
- data/lib/swarm_cli.rb +44 -0
- data/lib/swarm_memory/adapters/base.rb +141 -0
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
- data/lib/swarm_memory/chat_extension.rb +34 -0
- data/lib/swarm_memory/cli/commands.rb +306 -0
- data/lib/swarm_memory/core/entry.rb +37 -0
- data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
- data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
- data/lib/swarm_memory/core/path_normalizer.rb +75 -0
- data/lib/swarm_memory/core/semantic_index.rb +244 -0
- data/lib/swarm_memory/core/storage.rb +288 -0
- data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
- data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
- data/lib/swarm_memory/dsl/memory_config.rb +113 -0
- data/lib/swarm_memory/embeddings/embedder.rb +36 -0
- data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
- data/lib/swarm_memory/errors.rb +21 -0
- data/lib/swarm_memory/integration/cli_registration.rb +30 -0
- data/lib/swarm_memory/integration/configuration.rb +43 -0
- data/lib/swarm_memory/integration/registration.rb +31 -0
- data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
- data/lib/swarm_memory/optimization/analyzer.rb +244 -0
- data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
- data/lib/swarm_memory/prompts/memory.md.erb +109 -0
- data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
- data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
- data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
- data/lib/swarm_memory/search/semantic_search.rb +112 -0
- data/lib/swarm_memory/search/text_search.rb +42 -0
- data/lib/swarm_memory/search/text_similarity.rb +80 -0
- data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
- data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
- data/lib/swarm_memory/tools/load_skill.rb +313 -0
- data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
- data/lib/swarm_memory/tools/memory_delete.rb +99 -0
- data/lib/swarm_memory/tools/memory_edit.rb +185 -0
- data/lib/swarm_memory/tools/memory_glob.rb +160 -0
- data/lib/swarm_memory/tools/memory_grep.rb +247 -0
- data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
- data/lib/swarm_memory/tools/memory_read.rb +123 -0
- data/lib/swarm_memory/tools/memory_write.rb +231 -0
- data/lib/swarm_memory/utils.rb +50 -0
- data/lib/swarm_memory/version.rb +5 -0
- data/lib/swarm_memory.rb +166 -0
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
- data/lib/swarm_sdk/agent/builder.rb +461 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
- data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
- data/lib/swarm_sdk/agent/chat.rb +1159 -0
- data/lib/swarm_sdk/agent/context.rb +112 -0
- data/lib/swarm_sdk/agent/context_manager.rb +309 -0
- data/lib/swarm_sdk/agent/definition.rb +556 -0
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
- data/lib/swarm_sdk/configuration.rb +296 -0
- data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
- data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
- data/lib/swarm_sdk/context_compactor.rb +340 -0
- data/lib/swarm_sdk/hooks/adapter.rb +359 -0
- data/lib/swarm_sdk/hooks/context.rb +197 -0
- data/lib/swarm_sdk/hooks/definition.rb +80 -0
- data/lib/swarm_sdk/hooks/error.rb +29 -0
- data/lib/swarm_sdk/hooks/executor.rb +146 -0
- data/lib/swarm_sdk/hooks/registry.rb +147 -0
- data/lib/swarm_sdk/hooks/result.rb +150 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
- data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
- data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
- data/lib/swarm_sdk/log_collector.rb +51 -0
- data/lib/swarm_sdk/log_stream.rb +69 -0
- data/lib/swarm_sdk/markdown_parser.rb +75 -0
- data/lib/swarm_sdk/model_aliases.json +5 -0
- data/lib/swarm_sdk/models.json +1 -0
- data/lib/swarm_sdk/models.rb +120 -0
- data/lib/swarm_sdk/node/agent_config.rb +49 -0
- data/lib/swarm_sdk/node/builder.rb +439 -0
- data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
- data/lib/swarm_sdk/node_context.rb +170 -0
- data/lib/swarm_sdk/node_orchestrator.rb +384 -0
- data/lib/swarm_sdk/permissions/config.rb +239 -0
- data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
- data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
- data/lib/swarm_sdk/permissions/validator.rb +173 -0
- data/lib/swarm_sdk/permissions_builder.rb +122 -0
- data/lib/swarm_sdk/plugin.rb +147 -0
- data/lib/swarm_sdk/plugin_registry.rb +101 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
- data/lib/swarm_sdk/result.rb +97 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
- data/lib/swarm_sdk/swarm/builder.rb +586 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
- data/lib/swarm_sdk/swarm.rb +982 -0
- data/lib/swarm_sdk/tools/bash.rb +274 -0
- data/lib/swarm_sdk/tools/clock.rb +44 -0
- data/lib/swarm_sdk/tools/delegate.rb +164 -0
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
- data/lib/swarm_sdk/tools/edit.rb +150 -0
- data/lib/swarm_sdk/tools/glob.rb +158 -0
- data/lib/swarm_sdk/tools/grep.rb +228 -0
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
- data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
- data/lib/swarm_sdk/tools/read.rb +251 -0
- data/lib/swarm_sdk/tools/registry.rb +93 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
- data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
- data/lib/swarm_sdk/tools/think.rb +95 -0
- data/lib/swarm_sdk/tools/todo_write.rb +216 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
- data/lib/swarm_sdk/tools/write.rb +117 -0
- data/lib/swarm_sdk/utils.rb +50 -0
- data/lib/swarm_sdk/version.rb +5 -0
- data/lib/swarm_sdk.rb +157 -0
- data/llm.v2.txt +13407 -0
- data/rubocop/cop/security/no_reflection_methods.rb +47 -0
- data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
- data/swarm_cli.gemspec +57 -0
- data/swarm_memory.gemspec +28 -0
- data/swarm_sdk.gemspec +41 -0
- data/team.yml +1 -1
- data/team_full.yml +1875 -0
- data/{team_v2.yml → team_sdk.yml} +121 -52
- metadata +247 -4
- data/EXAMPLES.md +0 -164
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
module Tools
|
|
5
|
+
# Write tool for writing content to files
|
|
6
|
+
#
|
|
7
|
+
# Creates new files or overwrites existing files.
|
|
8
|
+
# Enforces read-before-write rule for existing files.
|
|
9
|
+
# Includes validation and usage guidelines via system reminders.
|
|
10
|
+
class Write < RubyLLM::Tool
|
|
11
|
+
include PathResolver
|
|
12
|
+
|
|
13
|
+
description <<~DESC
|
|
14
|
+
Writes a file to the local filesystem.
|
|
15
|
+
This tool will overwrite the existing file if there is one at the provided path.
|
|
16
|
+
If this is an existing file, you MUST use the Read tool first to read the file's contents.
|
|
17
|
+
This tool will fail if you did not read the file first.
|
|
18
|
+
ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
|
19
|
+
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
|
|
20
|
+
Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.
|
|
21
|
+
|
|
22
|
+
IMPORTANT - Path Handling:
|
|
23
|
+
- Relative paths (e.g., "tmp/file.txt", "src/main.rb") are resolved relative to your agent's working directory
|
|
24
|
+
- Absolute paths (e.g., "/tmp/file.txt", "/etc/passwd") are treated as system absolute paths
|
|
25
|
+
- When the user says "tmp/file.txt" they mean the tmp directory in your working directory, NOT /tmp
|
|
26
|
+
- Only use absolute paths (starting with /) when explicitly referring to system-level paths
|
|
27
|
+
DESC
|
|
28
|
+
|
|
29
|
+
param :file_path,
|
|
30
|
+
type: "string",
|
|
31
|
+
desc: "Path to the file. Use relative paths (e.g., 'tmp/file.txt') for files in your working directory, or absolute paths (e.g., '/etc/passwd') for system files.",
|
|
32
|
+
required: true
|
|
33
|
+
|
|
34
|
+
param :content,
|
|
35
|
+
type: "string",
|
|
36
|
+
desc: "The content to write to the file",
|
|
37
|
+
required: true
|
|
38
|
+
|
|
39
|
+
# Initialize the Write tool for a specific agent
|
|
40
|
+
#
|
|
41
|
+
# @param agent_name [Symbol, String] The agent identifier
|
|
42
|
+
# @param directory [String] Agent's working directory
|
|
43
|
+
def initialize(agent_name:, directory:)
|
|
44
|
+
super()
|
|
45
|
+
@agent_name = agent_name.to_sym
|
|
46
|
+
@directory = File.expand_path(directory)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Override name to return simple "Write" instead of full class path
|
|
50
|
+
def name
|
|
51
|
+
"Write"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def execute(file_path:, content:)
|
|
55
|
+
# Validate inputs
|
|
56
|
+
return validation_error("file_path is required") if file_path.nil? || file_path.to_s.strip.empty?
|
|
57
|
+
return validation_error("content is required") if content.nil?
|
|
58
|
+
|
|
59
|
+
# CRITICAL: Resolve path against agent directory
|
|
60
|
+
resolved_path = resolve_path(file_path)
|
|
61
|
+
|
|
62
|
+
# Check if file already exists (use resolved path)
|
|
63
|
+
file_exists = File.exist?(resolved_path)
|
|
64
|
+
|
|
65
|
+
# Enforce read-before-write for existing files (use resolved path)
|
|
66
|
+
if file_exists && !Stores::ReadTracker.file_read?(@agent_name, resolved_path)
|
|
67
|
+
return validation_error(
|
|
68
|
+
"Cannot write to existing file without reading it first. " \
|
|
69
|
+
"You must use the Read tool on '#{file_path}' before overwriting it. " \
|
|
70
|
+
"This ensures you have context about the file's current contents.",
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Create parent directory if it doesn't exist (use resolved path)
|
|
75
|
+
parent_dir = File.dirname(resolved_path)
|
|
76
|
+
FileUtils.mkdir_p(parent_dir) unless File.directory?(parent_dir)
|
|
77
|
+
|
|
78
|
+
# Write the file (use resolved path)
|
|
79
|
+
File.write(resolved_path, content, encoding: "UTF-8")
|
|
80
|
+
|
|
81
|
+
# Build success message
|
|
82
|
+
byte_size = content.bytesize
|
|
83
|
+
line_count = content.lines.count
|
|
84
|
+
action = file_exists ? "overwrote" : "created"
|
|
85
|
+
|
|
86
|
+
message = "Successfully #{action} file: #{file_path} (#{line_count} lines, #{byte_size} bytes)"
|
|
87
|
+
|
|
88
|
+
# Add system reminder for overwritten files
|
|
89
|
+
if file_exists
|
|
90
|
+
reminder = "<system-reminder>You overwrote an existing file. Make sure this was intentional and that you read the file first if you needed to preserve any content.</system-reminder>"
|
|
91
|
+
"#{message}\n\n#{reminder}"
|
|
92
|
+
else
|
|
93
|
+
message
|
|
94
|
+
end
|
|
95
|
+
rescue Errno::EACCES
|
|
96
|
+
error("Permission denied: Cannot write to file '#{file_path}'")
|
|
97
|
+
rescue Errno::EISDIR
|
|
98
|
+
error("Path is a directory, not a file.")
|
|
99
|
+
rescue Errno::ENOENT => e
|
|
100
|
+
error("Failed to create parent directory: #{e.message}")
|
|
101
|
+
rescue StandardError => e
|
|
102
|
+
error("Unexpected error writing file: #{e.class.name} - #{e.message}")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
# Helper methods
|
|
108
|
+
def validation_error(message)
|
|
109
|
+
"<tool_use_error>InputValidationError: #{message}</tool_use_error>"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def error(message)
|
|
113
|
+
"Error: #{message}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
# Shared utility methods for SwarmSDK
|
|
5
|
+
module Utils
|
|
6
|
+
class << self
|
|
7
|
+
# Recursively convert all hash keys to symbols
|
|
8
|
+
#
|
|
9
|
+
# Handles nested hashes and arrays containing hashes.
|
|
10
|
+
#
|
|
11
|
+
# @param obj [Object] Object to symbolize (Hash, Array, or other)
|
|
12
|
+
# @return [Object] Object with symbolized keys (if applicable)
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# Utils.symbolize_keys({ "name" => "test", "config" => { "key" => "value" } })
|
|
16
|
+
# # => { name: "test", config: { key: "value" } }
|
|
17
|
+
def symbolize_keys(obj)
|
|
18
|
+
case obj
|
|
19
|
+
when Hash
|
|
20
|
+
obj.transform_keys(&:to_sym).transform_values { |v| symbolize_keys(v) }
|
|
21
|
+
when Array
|
|
22
|
+
obj.map { |item| symbolize_keys(item) }
|
|
23
|
+
else
|
|
24
|
+
obj
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Recursively convert all hash keys to strings
|
|
29
|
+
#
|
|
30
|
+
# Handles nested hashes and arrays containing hashes.
|
|
31
|
+
#
|
|
32
|
+
# @param obj [Object] Object to stringify (Hash, Array, or other)
|
|
33
|
+
# @return [Object] Object with stringified keys (if applicable)
|
|
34
|
+
#
|
|
35
|
+
# @example
|
|
36
|
+
# Utils.stringify_keys({ name: "test", config: { key: "value" } })
|
|
37
|
+
# # => { "name" => "test", "config" => { "key" => "value" } }
|
|
38
|
+
def stringify_keys(obj)
|
|
39
|
+
case obj
|
|
40
|
+
when Hash
|
|
41
|
+
obj.transform_keys(&:to_s).transform_values { |v| stringify_keys(v) }
|
|
42
|
+
when Array
|
|
43
|
+
obj.map { |item| stringify_keys(item) }
|
|
44
|
+
else
|
|
45
|
+
obj
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/swarm_sdk.rb
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler"
|
|
4
|
+
require "digest"
|
|
5
|
+
require "English"
|
|
6
|
+
require "erb"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
require "json"
|
|
9
|
+
require "logger"
|
|
10
|
+
require "pathname"
|
|
11
|
+
require "securerandom"
|
|
12
|
+
require "set"
|
|
13
|
+
require "yaml"
|
|
14
|
+
|
|
15
|
+
require "async"
|
|
16
|
+
require "async/semaphore"
|
|
17
|
+
require "ruby_llm"
|
|
18
|
+
require "ruby_llm/mcp"
|
|
19
|
+
|
|
20
|
+
require_relative "swarm_sdk/version"
|
|
21
|
+
|
|
22
|
+
require "zeitwerk"
|
|
23
|
+
loader = Zeitwerk::Loader.new
|
|
24
|
+
loader.push_dir("#{__dir__}/swarm_sdk", namespace: SwarmSDK)
|
|
25
|
+
loader.inflector.inflect(
|
|
26
|
+
"cli" => "CLI",
|
|
27
|
+
)
|
|
28
|
+
loader.setup
|
|
29
|
+
|
|
30
|
+
# Load plugin system explicitly (core infrastructure)
|
|
31
|
+
require_relative "swarm_sdk/plugin"
|
|
32
|
+
require_relative "swarm_sdk/plugin_registry"
|
|
33
|
+
|
|
34
|
+
# Load custom providers explicitly (Zeitwerk doesn't eager load by default)
|
|
35
|
+
require_relative "swarm_sdk/providers/openai_with_responses"
|
|
36
|
+
|
|
37
|
+
module SwarmSDK
|
|
38
|
+
class Error < StandardError; end
|
|
39
|
+
class ConfigurationError < Error; end
|
|
40
|
+
class AgentNotFoundError < Error; end
|
|
41
|
+
class CircularDependencyError < Error; end
|
|
42
|
+
class ToolExecutionError < Error; end
|
|
43
|
+
class LLMError < Error; end
|
|
44
|
+
class StateError < Error; end
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
# Settings for SwarmSDK (global configuration)
|
|
48
|
+
attr_accessor :settings
|
|
49
|
+
|
|
50
|
+
# Main entry point for DSL
|
|
51
|
+
def build(&block)
|
|
52
|
+
Swarm::Builder.build(&block)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Configure SwarmSDK global settings
|
|
56
|
+
def configure
|
|
57
|
+
self.settings ||= Settings.new
|
|
58
|
+
yield(settings)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Reset settings to defaults
|
|
62
|
+
def reset_settings!
|
|
63
|
+
self.settings = Settings.new
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Alias for backward compatibility
|
|
67
|
+
alias_method :configuration, :settings
|
|
68
|
+
alias_method :reset_configuration!, :reset_settings!
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Settings class for SwarmSDK global settings (not to be confused with Configuration for YAML loading)
|
|
72
|
+
class Settings
|
|
73
|
+
# WebFetch tool LLM processing configuration
|
|
74
|
+
attr_accessor :webfetch_provider, :webfetch_model, :webfetch_base_url, :webfetch_max_tokens
|
|
75
|
+
|
|
76
|
+
def initialize
|
|
77
|
+
@webfetch_provider = nil
|
|
78
|
+
@webfetch_model = nil
|
|
79
|
+
@webfetch_base_url = nil
|
|
80
|
+
@webfetch_max_tokens = 4096
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Check if WebFetch LLM processing is enabled
|
|
84
|
+
def webfetch_llm_enabled?
|
|
85
|
+
!@webfetch_provider.nil? && !@webfetch_model.nil?
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Initialize default settings
|
|
90
|
+
self.settings = Settings.new
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Automatically configure RubyLLM from environment variables
|
|
94
|
+
# This makes SwarmSDK "just work" when users set standard ENV variables
|
|
95
|
+
RubyLLM.configure do |config|
|
|
96
|
+
# Only set if config not already set (||= handles nil ENV values gracefully)
|
|
97
|
+
|
|
98
|
+
# OpenAI
|
|
99
|
+
config.openai_api_key ||= ENV["OPENAI_API_KEY"]
|
|
100
|
+
config.openai_api_base ||= ENV["OPENAI_API_BASE"]
|
|
101
|
+
config.openai_organization_id ||= ENV["OPENAI_ORG_ID"]
|
|
102
|
+
config.openai_project_id ||= ENV["OPENAI_PROJECT_ID"]
|
|
103
|
+
|
|
104
|
+
# Anthropic
|
|
105
|
+
config.anthropic_api_key ||= ENV["ANTHROPIC_API_KEY"]
|
|
106
|
+
|
|
107
|
+
# Google Gemini
|
|
108
|
+
config.gemini_api_key ||= ENV["GEMINI_API_KEY"]
|
|
109
|
+
|
|
110
|
+
# Google Vertex AI (note: vertexai, not vertex_ai)
|
|
111
|
+
config.vertexai_project_id ||= ENV["GOOGLE_CLOUD_PROJECT"] || ENV["VERTEXAI_PROJECT_ID"]
|
|
112
|
+
config.vertexai_location ||= ENV["GOOGLE_CLOUD_LOCATION"] || ENV["VERTEXAI_LOCATION"]
|
|
113
|
+
|
|
114
|
+
# DeepSeek
|
|
115
|
+
config.deepseek_api_key ||= ENV["DEEPSEEK_API_KEY"]
|
|
116
|
+
|
|
117
|
+
# Mistral
|
|
118
|
+
config.mistral_api_key ||= ENV["MISTRAL_API_KEY"]
|
|
119
|
+
|
|
120
|
+
# Perplexity
|
|
121
|
+
config.perplexity_api_key ||= ENV["PERPLEXITY_API_KEY"]
|
|
122
|
+
|
|
123
|
+
# OpenRouter
|
|
124
|
+
config.openrouter_api_key ||= ENV["OPENROUTER_API_KEY"]
|
|
125
|
+
|
|
126
|
+
# AWS Bedrock
|
|
127
|
+
config.bedrock_api_key ||= ENV["AWS_ACCESS_KEY_ID"]
|
|
128
|
+
config.bedrock_secret_key ||= ENV["AWS_SECRET_ACCESS_KEY"]
|
|
129
|
+
config.bedrock_region ||= ENV["AWS_REGION"]
|
|
130
|
+
config.bedrock_session_token ||= ENV["AWS_SESSION_TOKEN"]
|
|
131
|
+
|
|
132
|
+
# Ollama (local)
|
|
133
|
+
config.ollama_api_base ||= ENV["OLLAMA_API_BASE"]
|
|
134
|
+
|
|
135
|
+
# GPUStack (local)
|
|
136
|
+
config.gpustack_api_base ||= ENV["GPUSTACK_API_BASE"]
|
|
137
|
+
config.gpustack_api_key ||= ENV["GPUSTACK_API_KEY"]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# monkey patches
|
|
141
|
+
# ruby_llm/mcp
|
|
142
|
+
# - add `id` when sending "notifications/initialized" message: https://github.com/patvice/ruby_llm-mcp/issues/65
|
|
143
|
+
# - remove `to_sym` on MCP parameter type: https://github.com/patvice/ruby_llm-mcp/issues/62#issuecomment-3421488406
|
|
144
|
+
require "ruby_llm/mcp/notifications/initialize"
|
|
145
|
+
require "ruby_llm/mcp/parameter"
|
|
146
|
+
|
|
147
|
+
module RubyLLM
|
|
148
|
+
module MCP
|
|
149
|
+
module Notifications
|
|
150
|
+
class Initialize
|
|
151
|
+
def call
|
|
152
|
+
@coordinator.request(notification_body, add_id: true, wait_for_response: false)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|