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,226 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- # Faraday middleware for capturing LLM API requests and responses
6
- #
7
- # This middleware intercepts HTTP calls to LLM providers and emits
8
- # structured events via LogStream for logging and monitoring.
9
- #
10
- # Events emitted:
11
- # - llm_api_request: Before sending request to LLM API
12
- # - llm_api_response: After receiving response from LLM API
13
- #
14
- # The middleware is injected at runtime into the provider's Faraday
15
- # connection stack (see Agent::Chat#inject_llm_instrumentation).
16
- class LLMInstrumentationMiddleware < Faraday::Middleware
17
- # Initialize middleware
18
- #
19
- # @param app [Faraday::Connection] Faraday app
20
- # @param on_request [Proc] Callback for request events
21
- # @param on_response [Proc] Callback for response events
22
- # @param provider_name [String] Provider name for logging
23
- def initialize(app, on_request:, on_response:, provider_name:)
24
- super(app)
25
- @on_request = on_request
26
- @on_response = on_response
27
- @provider_name = provider_name
28
- end
29
-
30
- # Intercept HTTP call
31
- #
32
- # @param env [Faraday::Env] Request environment
33
- # @return [Faraday::Response] HTTP response
34
- def call(env)
35
- start_time = Time.now
36
- accumulated_raw_chunks = []
37
-
38
- # Emit request event
39
- emit_request_event(env, start_time)
40
-
41
- # Wrap existing on_data to capture raw SSE chunks for streaming
42
- if env.request&.on_data
43
- original_on_data = env.request.on_data
44
- env.request.on_data = proc do |chunk, bytes, response_env|
45
- # Capture raw chunk BEFORE RubyLLM processes it
46
- accumulated_raw_chunks << chunk
47
- # Call original handler (RubyLLM's stream processing)
48
- original_on_data.call(chunk, bytes, response_env)
49
- end
50
- end
51
-
52
- # Execute request
53
- @app.call(env).on_complete do |response_env|
54
- end_time = Time.now
55
-
56
- # Determine if this was a streaming request based on whether chunks were accumulated
57
- # This is more reliable than parsing response content
58
- is_streaming = accumulated_raw_chunks.any?
59
-
60
- # For streaming: use accumulated raw SSE chunks
61
- # For non-streaming: use response body
62
- raw_body = is_streaming ? accumulated_raw_chunks.join : response_env.body
63
-
64
- # Store SSE body in Fiber-local for citation extraction
65
- # This allows append_citations_to_content to access the full SSE body
66
- # even though response.body is empty for streaming responses
67
- Fiber[:last_sse_body] = raw_body if is_streaming
68
-
69
- # Emit response event
70
- timing = { start_time: start_time, end_time: end_time, duration: end_time - start_time }
71
- emit_response_event(response_env, timing, raw_body, is_streaming)
72
- end
73
- end
74
-
75
- private
76
-
77
- # Emit request event
78
- #
79
- # @param env [Faraday::Env] Request environment
80
- # @param timestamp [Time] Request timestamp
81
- # @return [void]
82
- def emit_request_event(env, timestamp)
83
- request_data = {
84
- provider: @provider_name,
85
- url: env.url.to_s,
86
- body: parse_body(env.body),
87
- timestamp: timestamp.utc.iso8601,
88
- }
89
-
90
- @on_request.call(request_data)
91
- rescue StandardError => e
92
- # Don't let logging errors break the request
93
- LogStream.emit_error(e, source: "llm_instrumentation_middleware", context: "emit_request_event", provider: @provider_name)
94
- RubyLLM.logger.debug("LLM instrumentation request error: #{e.message}")
95
- end
96
-
97
- # Emit response event
98
- #
99
- # @param env [Faraday::Env] Response environment
100
- # @param timing [Hash] Timing information with :start_time, :end_time, :duration keys
101
- # @param raw_body [String, nil] Raw response body (SSE stream for streaming, JSON for non-streaming)
102
- # @param streaming [Boolean] Whether this was a streaming response (determined by chunk accumulation)
103
- # @return [void]
104
- def emit_response_event(env, timing, raw_body, streaming)
105
- response_data = {
106
- provider: @provider_name,
107
- body: parse_body(raw_body),
108
- streaming: streaming,
109
- duration_seconds: timing[:duration].round(3),
110
- timestamp: timing[:end_time].utc.iso8601,
111
- status: env.status,
112
- }
113
-
114
- # Extract usage information from response body if available
115
- if raw_body.is_a?(String) && !raw_body.empty?
116
- begin
117
- if streaming
118
- # For streaming, parse the LAST SSE event which contains usage
119
- # Skip "[DONE]" marker and find the last actual data event
120
- last_data_line = raw_body.split("\n").reverse.find { |l| l.start_with?("data:") && !l.include?("[DONE]") }
121
- if last_data_line
122
- parsed = JSON.parse(last_data_line.sub(/^data:\s*/, ""))
123
- response_data[:usage] = extract_usage(parsed) if parsed.is_a?(Hash)
124
- response_data[:model] = parsed["model"] if parsed.is_a?(Hash)
125
- end
126
- else
127
- # For non-streaming, parse the full JSON response
128
- parsed = JSON.parse(raw_body)
129
- response_data[:usage] = extract_usage(parsed) if parsed.is_a?(Hash)
130
- response_data[:model] = parsed["model"] if parsed.is_a?(Hash)
131
- response_data[:finish_reason] = extract_finish_reason(parsed) if parsed.is_a?(Hash)
132
- end
133
- rescue JSON::ParserError
134
- # Not JSON, skip usage extraction
135
- end
136
- end
137
-
138
- @on_response.call(response_data)
139
- rescue StandardError => e
140
- # Don't let logging errors break the response
141
- LogStream.emit_error(e, source: "llm_instrumentation_middleware", context: "emit_response_event", provider: @provider_name)
142
- RubyLLM.logger.debug("LLM instrumentation response error: #{e.message}")
143
- end
144
-
145
- # Sanitize headers by removing sensitive data
146
- #
147
- # @param headers [Hash] HTTP headers
148
- # @return [Hash] Sanitized headers
149
- def sanitize_headers(headers)
150
- return {} unless headers
151
-
152
- headers.transform_keys(&:to_s).transform_values do |value|
153
- # Redact authorization headers
154
- if value.to_s.match?(/bearer|token|key/i)
155
- "[REDACTED]"
156
- else
157
- value.to_s
158
- end
159
- end
160
- rescue StandardError
161
- {}
162
- end
163
-
164
- # Parse request/response body
165
- #
166
- # For requests: returns parsed JSON hash
167
- # For responses: returns full body (JSON parsed or raw string for SSE)
168
- #
169
- # @param body [String, Hash, nil] HTTP body
170
- # @return [Hash, String, nil] Parsed body
171
- def parse_body(body)
172
- return if body.nil? || body == ""
173
-
174
- # Already parsed
175
- return body if body.is_a?(Hash)
176
-
177
- # Try to parse JSON
178
- JSON.parse(body)
179
- rescue JSON::ParserError
180
- # Return full body for SSE/non-JSON responses
181
- # Don't truncate - let consumers decide how to handle large bodies
182
- body.to_s
183
- rescue StandardError
184
- nil
185
- end
186
-
187
- # Extract usage statistics from response
188
- #
189
- # Handles different provider formats (OpenAI, Anthropic, etc.)
190
- #
191
- # @param parsed [Hash] Parsed response body
192
- # @return [Hash, nil] Usage statistics
193
- def extract_usage(parsed)
194
- usage = parsed["usage"] || parsed.dig("usage")
195
- return unless usage
196
-
197
- {
198
- input_tokens: usage["input_tokens"] || usage["prompt_tokens"],
199
- output_tokens: usage["output_tokens"] || usage["completion_tokens"],
200
- total_tokens: usage["total_tokens"],
201
- }.compact
202
- rescue StandardError
203
- nil
204
- end
205
-
206
- # Extract finish reason from response
207
- #
208
- # Handles different provider formats
209
- #
210
- # @param parsed [Hash] Parsed response body
211
- # @return [String, nil] Finish reason
212
- def extract_finish_reason(parsed)
213
- # Anthropic format
214
- return parsed["stop_reason"] if parsed["stop_reason"]
215
-
216
- # OpenAI format
217
- choices = parsed["choices"]
218
- return unless choices&.is_a?(Array) && !choices.empty?
219
-
220
- choices.first["finish_reason"]
221
- rescue StandardError
222
- nil
223
- end
224
- end
225
- end
226
- end
@@ -1,173 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- # Builds system prompts for agents
6
- #
7
- # This class encapsulates all system prompt construction logic, including:
8
- # - Base system prompt rendering (for coding agents)
9
- # - Non-coding base prompt rendering
10
- # - Plugin prompt contribution collection
11
- # - Combining base and custom prompts
12
- #
13
- # ## Safety Note for SwarmMemory Integration
14
- #
15
- # This is an INTERNAL helper that receives Definition attributes as input.
16
- # Definition remains the single source of truth with all instance variables.
17
- # SwarmMemory uses `agent_definition.instance_eval { binding }` for ERB templating,
18
- # which requires all properties to be on Definition object. This helper is safe
19
- # because it doesn't affect Definition's structure - it only extracts logic.
20
- class SystemPromptBuilder
21
- BASE_SYSTEM_PROMPT_PATH = File.expand_path("../prompts/base_system_prompt.md.erb", __dir__)
22
-
23
- class << self
24
- # Build the complete system prompt for an agent
25
- #
26
- # @param custom_prompt [String, nil] Custom system prompt from configuration
27
- # @param coding_agent [Boolean] Whether agent is configured for coding tasks
28
- # @param disable_default_tools [Boolean, Array, nil] Default tools disable configuration
29
- # @param directory [String] Agent's working directory
30
- # @param definition [Definition] Full definition for plugin contributions
31
- # @param disable_environment_info [Boolean] Whether to omit environment info from prompt
32
- # @return [String] Complete system prompt
33
- def build(custom_prompt:, coding_agent:, disable_default_tools:, directory:, definition:,
34
- disable_environment_info: false)
35
- new(
36
- custom_prompt: custom_prompt,
37
- coding_agent: coding_agent,
38
- disable_default_tools: disable_default_tools,
39
- directory: directory,
40
- definition: definition,
41
- disable_environment_info: disable_environment_info,
42
- ).build
43
- end
44
- end
45
-
46
- def initialize(custom_prompt:, coding_agent:, disable_default_tools:, directory:, definition:,
47
- disable_environment_info: false)
48
- @custom_prompt = custom_prompt
49
- @coding_agent = coding_agent
50
- @disable_default_tools = disable_default_tools
51
- @directory = directory
52
- @definition = definition
53
- @disable_environment_info = disable_environment_info
54
- end
55
-
56
- def build
57
- prompt = base_prompt_section
58
- prompt = append_plugin_contributions(prompt)
59
- prompt
60
- end
61
-
62
- private
63
-
64
- def base_prompt_section
65
- if @coding_agent
66
- build_coding_agent_prompt
67
- elsif default_tools_enabled?
68
- build_non_coding_agent_prompt
69
- else
70
- (@custom_prompt || "").to_s
71
- end
72
- end
73
-
74
- def build_coding_agent_prompt
75
- rendered_base = render_base_system_prompt
76
-
77
- if @custom_prompt && !@custom_prompt.strip.empty?
78
- "#{rendered_base}\n\n#{@custom_prompt}"
79
- else
80
- rendered_base
81
- end
82
- end
83
-
84
- def build_non_coding_agent_prompt
85
- non_coding_base = render_non_coding_base_prompt
86
-
87
- if @custom_prompt && !@custom_prompt.strip.empty?
88
- if non_coding_base.empty?
89
- @custom_prompt.to_s
90
- else
91
- "#{non_coding_base}\n\n#{@custom_prompt}"
92
- end
93
- else
94
- non_coding_base
95
- end
96
- end
97
-
98
- def default_tools_enabled?
99
- @disable_default_tools != true
100
- end
101
-
102
- def render_base_system_prompt
103
- disable_environment_info = @disable_environment_info
104
- cwd = @directory || Dir.pwd
105
- platform = RUBY_PLATFORM
106
- os_version = begin
107
- %x(uname -sr 2>/dev/null).strip
108
- rescue
109
- RUBY_PLATFORM
110
- end
111
- date = Time.now.strftime("%Y-%m-%d")
112
-
113
- template_content = File.read(BASE_SYSTEM_PROMPT_PATH)
114
- ERB.new(template_content).result(binding)
115
- end
116
-
117
- def render_non_coding_base_prompt
118
- return "" if @disable_environment_info
119
-
120
- cwd = @directory || Dir.pwd
121
- platform = RUBY_PLATFORM
122
- os_version = begin
123
- %x(uname -sr 2>/dev/null).strip
124
- rescue
125
- RUBY_PLATFORM
126
- end
127
- date = Time.now.strftime("%Y-%m-%d")
128
-
129
- <<~PROMPT.strip
130
- # Today's date
131
-
132
- <today-date>
133
- #{date}
134
- #</today-date>
135
-
136
- # Current Environment
137
-
138
- <env>
139
- Working directory: #{cwd}
140
- Platform: #{platform}
141
- OS Version: #{os_version}
142
- </env>
143
- PROMPT
144
- end
145
-
146
- def append_plugin_contributions(prompt)
147
- contributions = collect_plugin_prompt_contributions
148
- return prompt if contributions.empty?
149
-
150
- combined_contributions = contributions.join("\n\n")
151
-
152
- if prompt && !prompt.strip.empty?
153
- "#{prompt}\n\n#{combined_contributions}"
154
- else
155
- combined_contributions
156
- end
157
- end
158
-
159
- def collect_plugin_prompt_contributions
160
- contributions = []
161
-
162
- PluginRegistry.all.each do |plugin|
163
- next unless plugin.memory_configured?(@definition)
164
-
165
- contribution = plugin.system_prompt_contribution(agent_definition: @definition, storage: nil)
166
- contributions << contribution if contribution && !contribution.strip.empty?
167
- end
168
-
169
- contributions
170
- end
171
- end
172
- end
173
- end
@@ -1,189 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- # Per-agent tool registry managing available and active tools
6
- #
7
- # ## Architecture
8
- #
9
- # - **Available tools**: All tool instances the agent CAN use (registry)
10
- # - **Active tools**: Subset sent to LLM based on skill state
11
- #
12
- # ## Thread Safety
13
- #
14
- # Registry access is protected by Async::Semaphore for fiber-safe operations.
15
- #
16
- # @example Registering tools
17
- # registry = ToolRegistry.new
18
- # registry.register(Read.new, source: :builtin)
19
- # registry.register(delegate_tool, source: :delegation, metadata: { delegate_name: :backend })
20
- #
21
- # @example Getting active tools (no skill)
22
- # active = registry.active_tools(skill_state: nil)
23
- # # Returns ALL available tools
24
- #
25
- # @example Getting active tools (with skill)
26
- # skill_state = SkillState.new( # From SwarmMemory plugin
27
- # file_path: "skill/audit.md",
28
- # tools: ["Read", "Grep"],
29
- # permissions: { "Bash" => { deny_commands: ["rm"] } }
30
- # )
31
- # active = registry.active_tools(skill_state: skill_state)
32
- # # Returns: skill's tools + non-removable tools
33
- class ToolRegistry
34
- # Tool metadata stored in registry
35
- #
36
- # @!attribute instance [r] Tool instance (possibly wrapped with permissions)
37
- # @!attribute base_instance [r] Unwrapped tool instance (for skill permission override)
38
- # @!attribute removable [r] Can be deactivated by skills
39
- # @!attribute source [r] Tool source (:builtin, :delegation, :mcp, :plugin, :custom)
40
- # @!attribute metadata [r] Source-specific metadata
41
- ToolEntry = Data.define(:instance, :base_instance, :removable, :source, :metadata)
42
-
43
- def initialize
44
- @available_tools = {} # String name => ToolEntry
45
- @mutex = Async::Semaphore.new(1) # Fiber-safe mutex
46
- end
47
-
48
- # Register a tool in the available tools registry
49
- #
50
- # @param tool [RubyLLM::Tool] Tool instance (possibly wrapped)
51
- # @param base_tool [RubyLLM::Tool, nil] Unwrapped instance (for permission override)
52
- # @param source [Symbol] Tool source (:builtin, :delegation, :mcp, :plugin, :custom)
53
- # @param metadata [Hash] Source-specific metadata (server_name, plugin_name, etc.)
54
- # @return [void]
55
- #
56
- # @example Register builtin tool
57
- # registry.register(Read.new, source: :builtin)
58
- #
59
- # @example Register delegation tool
60
- # registry.register(delegate_tool, source: :delegation, metadata: { delegate_name: :backend })
61
- #
62
- # @example Register MCP tool
63
- # registry.register(mcp_tool, source: :mcp, metadata: { server_name: "codebase" })
64
- #
65
- # @example Register with permission wrapper
66
- # wrapped_tool = PermissionWrapper.new(base_tool, permissions)
67
- # registry.register(wrapped_tool, base_tool: base_tool, source: :builtin)
68
- def register(tool, base_tool: nil, source:, metadata: {})
69
- @mutex.acquire do
70
- # Infer removability from tool class
71
- removable = tool.respond_to?(:removable?) ? tool.removable? : true
72
-
73
- @available_tools[tool.name] = ToolEntry.new(
74
- instance: tool,
75
- base_instance: base_tool || tool, # If no base, use same instance
76
- removable: removable,
77
- source: source,
78
- metadata: metadata,
79
- )
80
- end
81
- end
82
-
83
- # Unregister a tool (for testing/cleanup)
84
- #
85
- # @param name [String, Symbol] Tool name
86
- # @return [ToolEntry, nil] Removed entry
87
- def unregister(name)
88
- @mutex.acquire do
89
- @available_tools.delete(name.to_s)
90
- end
91
- end
92
-
93
- # Get active tools based on skill state
94
- #
95
- # Returns Hash of tool instances ready for RubyLLM::Chat.
96
- #
97
- # Logic:
98
- # - If skill_state is nil: Return ALL available tools
99
- # - If skill_state restricts tools: Return skill's tools + non-removable tools
100
- # - Skill permissions are applied during activation (wrapping base_instance)
101
- #
102
- # @param skill_state [Object, nil] Skill state object (from plugin), or nil for all
103
- # @param tool_configurator [ToolConfigurator, nil] For permission wrapping
104
- # @param agent_definition [Agent::Definition, nil] For permission wrapping
105
- # @return [Hash{String => RubyLLM::Tool}] name => instance mapping
106
- #
107
- # @example No skill loaded - all tools
108
- # registry.active_tools(skill_state: nil)
109
- # # => { "Read" => <Read>, "WorkWithBackend" => <Delegate>, ... }
110
- #
111
- # @example Skill loaded with focused toolset
112
- # registry.active_tools(skill_state: skill_state)
113
- # # => { "Read" => <Read>, "WorkWithBackend" => <Delegate>, "Think" => <Think>, "MemoryRead" => <MemoryRead> }
114
- # # Includes: requested tools + non-removable tools
115
- def active_tools(skill_state: nil, tool_configurator: nil, agent_definition: nil)
116
- @mutex.acquire do
117
- result = if skill_state&.restricts_tools?
118
- # Skill loaded with tool restriction - only skill's tools + non-removable
119
- filtered = {}
120
-
121
- # Always include non-removable tools (use wrapped instance)
122
- @available_tools.each do |name, entry|
123
- filtered[name] = entry.instance unless entry.removable
124
- end
125
-
126
- # Add requested tools from skill
127
- skill_state.tools.each do |name|
128
- entry = @available_tools[name.to_s]
129
- next unless entry
130
-
131
- # Check if skill has custom permissions for this tool
132
- skill_permissions = skill_state.permissions_for(name)
133
-
134
- if skill_permissions && tool_configurator && agent_definition
135
- # Skill overrides permissions - wrap the BASE instance
136
- wrapped = tool_configurator.wrap_tool_with_permissions(
137
- entry.base_instance,
138
- skill_permissions,
139
- agent_definition,
140
- )
141
- filtered[name.to_s] = wrapped
142
- else
143
- # No skill permission override - use registered instance
144
- filtered[name.to_s] = entry.instance
145
- end
146
- end
147
-
148
- filtered
149
- else
150
- # No skill OR skill doesn't restrict tools - all available tools
151
- @available_tools.transform_values(&:instance)
152
- end
153
-
154
- result
155
- end
156
- end
157
-
158
- # Check if tool exists in registry
159
- #
160
- # @param name [String, Symbol] Tool name
161
- # @return [Boolean]
162
- def has_tool?(name)
163
- @available_tools.key?(name.to_s)
164
- end
165
-
166
- # Get all available tool names
167
- #
168
- # @return [Array<String>]
169
- def tool_names
170
- @available_tools.keys
171
- end
172
-
173
- # Get tool entry with metadata
174
- #
175
- # @param name [String, Symbol] Tool name
176
- # @return [ToolEntry, nil]
177
- def get(name)
178
- @available_tools[name.to_s]
179
- end
180
-
181
- # Get all non-removable tool names
182
- #
183
- # @return [Array<String>]
184
- def non_removable_tool_names
185
- @available_tools.select { |_name, entry| !entry.removable }.keys
186
- end
187
- end
188
- end
189
- end