swarm_memory 2.1.5 → 2.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. metadata +5 -184
  4. data/lib/claude_swarm/base_executor.rb +0 -133
  5. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  6. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  7. data/lib/claude_swarm/cli.rb +0 -697
  8. data/lib/claude_swarm/commands/ps.rb +0 -215
  9. data/lib/claude_swarm/commands/show.rb +0 -139
  10. data/lib/claude_swarm/configuration.rb +0 -373
  11. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  12. data/lib/claude_swarm/json_handler.rb +0 -91
  13. data/lib/claude_swarm/mcp_generator.rb +0 -230
  14. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  15. data/lib/claude_swarm/openai/executor.rb +0 -256
  16. data/lib/claude_swarm/openai/responses.rb +0 -319
  17. data/lib/claude_swarm/orchestrator.rb +0 -878
  18. data/lib/claude_swarm/process_tracker.rb +0 -78
  19. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  20. data/lib/claude_swarm/session_path.rb +0 -42
  21. data/lib/claude_swarm/settings_generator.rb +0 -77
  22. data/lib/claude_swarm/system_utils.rb +0 -46
  23. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  24. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  25. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  27. data/lib/claude_swarm/version.rb +0 -5
  28. data/lib/claude_swarm/worktree_manager.rb +0 -475
  29. data/lib/claude_swarm/yaml_loader.rb +0 -22
  30. data/lib/claude_swarm.rb +0 -67
  31. data/lib/swarm_cli/cli.rb +0 -201
  32. data/lib/swarm_cli/command_registry.rb +0 -61
  33. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  34. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  35. data/lib/swarm_cli/commands/migrate.rb +0 -55
  36. data/lib/swarm_cli/commands/run.rb +0 -173
  37. data/lib/swarm_cli/config_loader.rb +0 -98
  38. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  39. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  40. data/lib/swarm_cli/interactive_repl.rb +0 -924
  41. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  42. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  43. data/lib/swarm_cli/migrate_options.rb +0 -54
  44. data/lib/swarm_cli/migrator.rb +0 -132
  45. data/lib/swarm_cli/options.rb +0 -151
  46. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  47. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  48. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  49. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  50. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  51. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  52. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  53. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  54. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  55. data/lib/swarm_cli/ui/icons.rb +0 -36
  56. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  57. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  58. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  59. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  60. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  61. data/lib/swarm_cli/version.rb +0 -5
  62. data/lib/swarm_cli.rb +0 -46
  63. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  64. data/lib/swarm_sdk/agent/builder.rb +0 -552
  65. data/lib/swarm_sdk/agent/chat.rb +0 -774
  66. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  67. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  68. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  69. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  70. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  71. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  72. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  73. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  75. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  76. data/lib/swarm_sdk/agent/context.rb +0 -116
  77. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  78. data/lib/swarm_sdk/agent/definition.rb +0 -477
  79. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  80. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  81. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  82. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  83. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  84. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  85. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  86. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  87. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  88. data/lib/swarm_sdk/configuration.rb +0 -135
  89. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  90. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  91. data/lib/swarm_sdk/context_compactor.rb +0 -335
  92. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  93. data/lib/swarm_sdk/context_management/context.rb +0 -328
  94. data/lib/swarm_sdk/defaults.rb +0 -196
  95. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  96. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  97. data/lib/swarm_sdk/hooks/context.rb +0 -197
  98. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  99. data/lib/swarm_sdk/hooks/error.rb +0 -29
  100. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  101. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  102. data/lib/swarm_sdk/hooks/result.rb +0 -150
  103. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  104. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  105. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  106. data/lib/swarm_sdk/log_collector.rb +0 -227
  107. data/lib/swarm_sdk/log_stream.rb +0 -127
  108. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  109. data/lib/swarm_sdk/model_aliases.json +0 -8
  110. data/lib/swarm_sdk/models.json +0 -1
  111. data/lib/swarm_sdk/models.rb +0 -120
  112. data/lib/swarm_sdk/node_context.rb +0 -245
  113. data/lib/swarm_sdk/observer/builder.rb +0 -81
  114. data/lib/swarm_sdk/observer/config.rb +0 -45
  115. data/lib/swarm_sdk/observer/manager.rb +0 -236
  116. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  117. data/lib/swarm_sdk/permissions/config.rb +0 -239
  118. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  119. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  120. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  121. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  122. data/lib/swarm_sdk/plugin.rb +0 -309
  123. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  124. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  125. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  126. data/lib/swarm_sdk/restore_result.rb +0 -65
  127. data/lib/swarm_sdk/result.rb +0 -123
  128. data/lib/swarm_sdk/snapshot.rb +0 -156
  129. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  130. data/lib/swarm_sdk/state_restorer.rb +0 -476
  131. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  132. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  133. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  134. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  135. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  136. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  141. data/lib/swarm_sdk/swarm.rb +0 -717
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/bash.rb +0 -282
  145. data/lib/swarm_sdk/tools/clock.rb +0 -44
  146. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  147. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  148. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  149. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  150. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  151. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  152. data/lib/swarm_sdk/tools/edit.rb +0 -145
  153. data/lib/swarm_sdk/tools/glob.rb +0 -166
  154. data/lib/swarm_sdk/tools/grep.rb +0 -235
  155. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  156. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  157. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -98
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/utils.rb +0 -68
  174. data/lib/swarm_sdk/validation_result.rb +0 -33
  175. data/lib/swarm_sdk/version.rb +0 -5
  176. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  177. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  178. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  179. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  180. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  181. data/lib/swarm_sdk/workflow.rb +0 -554
  182. data/lib/swarm_sdk.rb +0 -524
@@ -1,233 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- module ChatHelpers
6
- # LLM configuration and provider setup
7
- #
8
- # Extracted from Chat to reduce class size and centralize RubyLLM setup logic.
9
- module LlmConfiguration
10
- private
11
-
12
- # Create the internal RubyLLM::Chat instance
13
- #
14
- # @return [RubyLLM::Chat] Chat instance
15
- def create_llm_chat(model_id:, provider_name:, base_url:, api_version:, timeout:, assume_model_exists:, max_concurrent_tools:)
16
- chat_options = build_chat_options(max_concurrent_tools)
17
-
18
- chat = instantiate_chat(
19
- model_id: model_id,
20
- provider_name: provider_name,
21
- base_url: base_url,
22
- timeout: timeout,
23
- assume_model_exists: assume_model_exists,
24
- chat_options: chat_options,
25
- )
26
-
27
- # Enable RubyLLM's native Responses API if configured
28
- enable_responses_api(chat, api_version, base_url) if api_version == "v1/responses"
29
-
30
- chat
31
- end
32
-
33
- # Build chat options hash
34
- #
35
- # @param max_concurrent_tools [Integer, nil] Max concurrent tool executions
36
- # @return [Hash] Chat options
37
- def build_chat_options(max_concurrent_tools)
38
- return {} unless max_concurrent_tools
39
-
40
- {
41
- tool_concurrency: :async,
42
- max_concurrency: max_concurrent_tools,
43
- }
44
- end
45
-
46
- # Instantiate RubyLLM::Chat with appropriate configuration
47
- #
48
- # @return [RubyLLM::Chat] Chat instance
49
- def instantiate_chat(model_id:, provider_name:, base_url:, timeout:, assume_model_exists:, chat_options:)
50
- if base_url || timeout != Defaults::Timeouts::AGENT_REQUEST_SECONDS
51
- instantiate_with_custom_context(
52
- model_id: model_id,
53
- provider_name: provider_name,
54
- base_url: base_url,
55
- timeout: timeout,
56
- assume_model_exists: assume_model_exists,
57
- chat_options: chat_options,
58
- )
59
- elsif provider_name
60
- instantiate_with_provider(
61
- model_id: model_id,
62
- provider_name: provider_name,
63
- assume_model_exists: assume_model_exists,
64
- chat_options: chat_options,
65
- )
66
- else
67
- instantiate_default(
68
- model_id: model_id,
69
- assume_model_exists: assume_model_exists,
70
- chat_options: chat_options,
71
- )
72
- end
73
- end
74
-
75
- # Instantiate chat with custom context (base_url/timeout overrides)
76
- def instantiate_with_custom_context(model_id:, provider_name:, base_url:, timeout:, assume_model_exists:, chat_options:)
77
- raise ArgumentError, "Provider must be specified when base_url is set" if base_url && !provider_name
78
-
79
- context = build_custom_context(provider: provider_name, base_url: base_url, timeout: timeout)
80
- assume_model_exists = base_url ? true : false if assume_model_exists.nil?
81
-
82
- RubyLLM.chat(
83
- model: model_id,
84
- provider: provider_name,
85
- assume_model_exists: assume_model_exists,
86
- context: context,
87
- **chat_options,
88
- )
89
- end
90
-
91
- # Instantiate chat with explicit provider
92
- def instantiate_with_provider(model_id:, provider_name:, assume_model_exists:, chat_options:)
93
- assume_model_exists = false if assume_model_exists.nil?
94
-
95
- RubyLLM.chat(
96
- model: model_id,
97
- provider: provider_name,
98
- assume_model_exists: assume_model_exists,
99
- **chat_options,
100
- )
101
- end
102
-
103
- # Instantiate chat with default configuration
104
- def instantiate_default(model_id:, assume_model_exists:, chat_options:)
105
- assume_model_exists = false if assume_model_exists.nil?
106
-
107
- RubyLLM.chat(
108
- model: model_id,
109
- assume_model_exists: assume_model_exists,
110
- **chat_options,
111
- )
112
- end
113
-
114
- # Build custom RubyLLM context for base_url/timeout overrides
115
- #
116
- # @return [RubyLLM::Context] Configured context
117
- def build_custom_context(provider:, base_url:, timeout:)
118
- RubyLLM.context do |config|
119
- config.request_timeout = timeout
120
-
121
- configure_provider_base_url(config, provider, base_url) if base_url
122
- end
123
- end
124
-
125
- # Configure provider-specific base URL
126
- def configure_provider_base_url(config, provider, base_url)
127
- case provider.to_s
128
- when "openai", "deepseek", "perplexity", "mistral", "openrouter"
129
- config.openai_api_base = base_url
130
- config.openai_api_key = ENV["OPENAI_API_KEY"] || "dummy-key-for-local"
131
- config.openai_use_system_role = true
132
- when "ollama"
133
- config.ollama_api_base = base_url
134
- when "gpustack"
135
- config.gpustack_api_base = base_url
136
- config.gpustack_api_key = ENV["GPUSTACK_API_KEY"] || "dummy-key"
137
- else
138
- raise ArgumentError, "Provider '#{provider}' doesn't support custom base_url."
139
- end
140
- end
141
-
142
- # Fetch real model info for accurate context tracking
143
- #
144
- # @param model_id [String] Model ID to lookup
145
- def fetch_real_model_info(model_id)
146
- @model_lookup_error = nil
147
- @real_model_info = begin
148
- RubyLLM.models.find(model_id)
149
- rescue StandardError => e
150
- suggestions = suggest_similar_models(model_id)
151
- @model_lookup_error = {
152
- model: model_id,
153
- error_message: e.message,
154
- suggestions: suggestions,
155
- }
156
- nil
157
- end
158
- end
159
-
160
- # Enable RubyLLM's native Responses API on the chat instance
161
- #
162
- # Uses RubyLLM's built-in support for OpenAI's Responses API (v1/responses endpoint)
163
- # which provides automatic stateful conversation tracking with 5-minute TTL.
164
- #
165
- # @param chat [RubyLLM::Chat] Chat instance to configure
166
- # @param api_version [String] API version (should be "v1/responses")
167
- # @param base_url [String, nil] Custom endpoint URL if any
168
- def enable_responses_api(chat, api_version, base_url)
169
- return unless api_version == "v1/responses"
170
-
171
- # Warn if using custom endpoint (typically doesn't support Responses API)
172
- if base_url && !base_url.include?("api.openai.com")
173
- RubyLLM.logger.warn(
174
- "SwarmSDK: Responses API requested but using custom endpoint #{base_url}. " \
175
- "Custom endpoints typically don't support /v1/responses.",
176
- )
177
- end
178
-
179
- # Enable native RubyLLM Responses API support
180
- # - stateful: true enables automatic previous_response_id tracking
181
- # - store: true enables server-side conversation storage
182
- chat.with_responses_api(stateful: true, store: true)
183
- RubyLLM.logger.debug("SwarmSDK: Enabled native Responses API support")
184
- end
185
-
186
- # Configure LLM parameters with proper temperature normalization
187
- #
188
- # @param params [Hash] Parameter hash
189
- # @return [self]
190
- def configure_parameters(params)
191
- return self if params.nil? || params.empty?
192
-
193
- if params[:temperature]
194
- @llm_chat.with_temperature(params[:temperature])
195
- params = params.except(:temperature)
196
- end
197
-
198
- @llm_chat.with_params(**params) if params.any?
199
-
200
- self
201
- end
202
-
203
- # Configure custom HTTP headers for LLM requests
204
- #
205
- # @param headers [Hash, nil] Custom HTTP headers
206
- # @return [self]
207
- def configure_headers(custom_headers)
208
- return self if custom_headers.nil? || custom_headers.empty?
209
-
210
- @llm_chat.with_headers(**custom_headers)
211
-
212
- self
213
- end
214
-
215
- # Suggest similar models when a model is not found
216
- #
217
- # @param query [String] Model name to search for
218
- # @return [Array<RubyLLM::Model::Info>] Up to 3 similar models
219
- def suggest_similar_models(query)
220
- normalized_query = query.to_s.downcase.gsub(/[.\-_]/, "")
221
-
222
- RubyLLM.models.all.select do |model_info|
223
- normalized_id = model_info.id.downcase.gsub(/[.\-_]/, "")
224
- normalized_id.include?(normalized_query) ||
225
- model_info.name&.downcase&.gsub(/[.\-_]/, "")&.include?(normalized_query)
226
- end.first(3)
227
- rescue StandardError
228
- []
229
- end
230
- end
231
- end
232
- end
233
- end
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- module ChatHelpers
6
- # Helper methods for logging and serialization of tool calls and results
7
- #
8
- # Responsibilities:
9
- # - Format tool calls for logging
10
- # - Serialize tool results (handling different types)
11
- # - Calculate LLM costs based on token usage
12
- #
13
- # These are stateless utility methods that operate on data structures.
14
- module LoggingHelpers
15
- # Format tool calls for logging
16
- #
17
- # @param tool_calls_hash [Hash] Tool calls from message
18
- # @return [Array<Hash>, nil] Formatted tool calls
19
- def format_tool_calls(tool_calls_hash)
20
- return unless tool_calls_hash
21
-
22
- tool_calls_hash.map do |_id, tc|
23
- {
24
- id: tc.id,
25
- name: tc.name,
26
- arguments: tc.arguments,
27
- }
28
- end
29
- end
30
-
31
- # Serialize a tool result for logging
32
- #
33
- # Handles multiple result types:
34
- # - String: pass through
35
- # - Hash/Array: pass through
36
- # - RubyLLM::Content: extract text and attachment info
37
- # - Other: convert to string
38
- #
39
- # @param result [String, Hash, Array, RubyLLM::Content, Object] Tool result
40
- # @return [String, Hash, Array] Serialized result
41
- def serialize_result(result)
42
- case result
43
- when String then result
44
- when Hash, Array then result
45
- when RubyLLM::Content
46
- # Format Content objects to show text and attachment info
47
- parts = []
48
- parts << result.text if result.text && !result.text.empty?
49
-
50
- if result.attachments.any?
51
- attachment_info = result.attachments.map do |att|
52
- "#{att.source} (#{att.mime_type})"
53
- end.join(", ")
54
- parts << "[Attachments: #{attachment_info}]"
55
- end
56
-
57
- parts.join(" ")
58
- else
59
- result.to_s
60
- end
61
- end
62
-
63
- # Calculate LLM cost for a message
64
- #
65
- # Uses RubyLLM's model registry to get pricing information.
66
- # Returns zero cost if pricing is unavailable.
67
- #
68
- # @param message [RubyLLM::Message] Message with token counts
69
- # @return [Hash] Cost breakdown { input_cost:, output_cost:, total_cost: }
70
- def calculate_cost(message)
71
- return zero_cost unless message.input_tokens && message.output_tokens
72
-
73
- # Use SwarmSDK's model registry (not RubyLLM's) for up-to-date pricing
74
- model_info = SwarmSDK::Models.find(message.model_id)
75
- return zero_cost unless model_info
76
-
77
- # Extract pricing from SwarmSDK's models.json structure
78
- pricing = model_info["pricing"] || model_info[:pricing]
79
- return zero_cost unless pricing
80
-
81
- text_pricing = pricing["text_tokens"] || pricing[:text_tokens]
82
- return zero_cost unless text_pricing
83
-
84
- standard_pricing = text_pricing["standard"] || text_pricing[:standard]
85
- return zero_cost unless standard_pricing
86
-
87
- input_price = standard_pricing["input_per_million"] || standard_pricing[:input_per_million]
88
- output_price = standard_pricing["output_per_million"] || standard_pricing[:output_per_million]
89
-
90
- return zero_cost unless input_price && output_price
91
-
92
- # Calculate costs (prices are per million tokens in USD)
93
- input_cost = (message.input_tokens / 1_000_000.0) * input_price
94
- output_cost = (message.output_tokens / 1_000_000.0) * output_price
95
-
96
- {
97
- input_cost: input_cost,
98
- output_cost: output_cost,
99
- total_cost: input_cost + output_cost,
100
- }
101
- rescue StandardError => e
102
- # Model not found in registry or pricing not available
103
- RubyLLM.logger.debug("Cost calculation failed for #{message.model_id}: #{e.message}")
104
- zero_cost
105
- end
106
-
107
- # Zero cost fallback
108
- #
109
- # @return [Hash] Zero cost breakdown
110
- def zero_cost
111
- { input_cost: 0.0, output_cost: 0.0, total_cost: 0.0 }
112
- end
113
- end
114
- end
115
- end
116
- end
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- module ChatHelpers
6
- # Message serialization and deserialization for snapshots
7
- #
8
- # Extracted from Chat to reduce class size and centralize persistence logic.
9
- module Serialization
10
- # Create snapshot of current conversation state
11
- #
12
- # @return [Hash] Serialized conversation data
13
- def conversation_snapshot
14
- {
15
- messages: @llm_chat.messages.map { |msg| serialize_message(msg) },
16
- model_id: model_id,
17
- provider: model_provider,
18
- timestamp: Time.now.utc.iso8601,
19
- }
20
- end
21
-
22
- # Restore conversation from snapshot
23
- #
24
- # @param snapshot [Hash] Serialized conversation data
25
- # @return [self]
26
- def restore_conversation(snapshot)
27
- raise ArgumentError, "Invalid snapshot: missing messages" unless snapshot[:messages]
28
-
29
- @llm_chat.messages.clear
30
- snapshot[:messages].each do |msg_data|
31
- @llm_chat.messages << deserialize_message(msg_data)
32
- end
33
-
34
- self
35
- end
36
-
37
- private
38
-
39
- # Serialize a RubyLLM::Message to a plain hash
40
- #
41
- # @param message [RubyLLM::Message] Message to serialize
42
- # @return [Hash] Serialized message data
43
- def serialize_message(message)
44
- data = message.to_h
45
-
46
- # Convert tool_calls to plain hashes (they're ToolCall objects)
47
- if data[:tool_calls]
48
- data[:tool_calls] = data[:tool_calls].transform_values(&:to_h)
49
- end
50
-
51
- # Handle Content objects
52
- if data[:content].respond_to?(:to_h)
53
- data[:content] = data[:content].to_h
54
- end
55
-
56
- data
57
- end
58
-
59
- # Deserialize a hash back to a RubyLLM::Message
60
- #
61
- # @param data [Hash] Serialized message data
62
- # @return [RubyLLM::Message] Reconstructed message
63
- def deserialize_message(data)
64
- data = data.transform_keys(&:to_sym)
65
-
66
- # Convert tool_calls back to ToolCall objects
67
- if data[:tool_calls]
68
- data[:tool_calls] = data[:tool_calls].transform_values do |tc_data|
69
- tc_data = tc_data.transform_keys(&:to_sym)
70
- RubyLLM::ToolCall.new(
71
- id: tc_data[:id],
72
- name: tc_data[:name],
73
- arguments: tc_data[:arguments] || {},
74
- )
75
- end
76
- end
77
-
78
- RubyLLM::Message.new(**data)
79
- end
80
- end
81
- end
82
- end
83
- end
@@ -1,136 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- module ChatHelpers
6
- # Handles injection of system reminders at strategic points in the conversation
7
- #
8
- # Responsibilities:
9
- # - Inject reminders before/after first user message
10
- # - Inject periodic TodoWrite reminders
11
- # - Track when reminders were last injected
12
- #
13
- # This class is stateless - it operates on the chat's message history.
14
- class SystemReminderInjector
15
- # System reminder to inject AFTER the first user message
16
- AFTER_FIRST_MESSAGE_REMINDER = <<~REMINDER.strip
17
- <system-reminder>Your todo list is currently empty. DO NOT mention this to the user. If this task requires multiple steps: (1) FIRST analyze the scope by searching/reading files, (2) SECOND create a COMPLETE todo list with ALL tasks before starting work, (3) THIRD execute tasks one by one. Only skip the todo list for simple single-step tasks. Do not mention this message to the user.</system-reminder>
18
- REMINDER
19
-
20
- # Periodic reminder about TodoWrite tool usage
21
- TODOWRITE_PERIODIC_REMINDER = <<~REMINDER.strip
22
- <system-reminder>The TodoWrite tool hasn't been used recently. If you're working on tasks that would benefit from tracking progress, consider using the TodoWrite tool to track progress. Also consider cleaning up the todo list if has become stale and no longer matches what you are working on. Only use it if it's relevant to the current work. This is just a gentle reminder - ignore if not applicable.</system-reminder>
23
- REMINDER
24
-
25
- # Backward compatibility alias - use Defaults module for new code
26
- TODOWRITE_REMINDER_INTERVAL = Defaults::Context::TODOWRITE_REMINDER_INTERVAL
27
-
28
- class << self
29
- # Check if this is the first user message in the conversation
30
- #
31
- # @param chat [Agent::Chat] The chat instance
32
- # @return [Boolean] true if no user messages exist yet
33
- def first_message?(chat)
34
- !chat.has_user_message?
35
- end
36
-
37
- # Inject first message reminders
38
- #
39
- # This manually constructs the first message sequence with system reminders.
40
- #
41
- # Sequence:
42
- # 1. User's actual prompt
43
- # 2. Toolset reminder (list of available tools)
44
- # 3. AFTER_FIRST_MESSAGE_REMINDER (todo list reminder - only if TodoWrite available)
45
- #
46
- # @param chat [Agent::Chat] The chat instance
47
- # @param prompt [String] The user's actual prompt
48
- # @return [void]
49
- def inject_first_message_reminders(chat, prompt)
50
- # Build user message with embedded reminders
51
- # Reminders are embedded in the content, not separate messages
52
- parts = [
53
- prompt,
54
- build_toolset_reminder(chat),
55
- ]
56
-
57
- # Only include todo list reminder if agent has TodoWrite tool
58
- parts << AFTER_FIRST_MESSAGE_REMINDER if chat.has_tool?("TodoWrite")
59
-
60
- full_content = parts.join("\n\n")
61
-
62
- # Extract reminders and add clean prompt to persistent history
63
- reminders = chat.context_manager.extract_system_reminders(full_content)
64
- clean_prompt = chat.context_manager.strip_system_reminders(full_content)
65
-
66
- # Store clean prompt (without reminders) in conversation history
67
- chat.add_message(role: :user, content: clean_prompt)
68
-
69
- # Track reminders to embed in this message when sending to LLM
70
- reminders.each do |reminder|
71
- chat.add_ephemeral_reminder(reminder)
72
- end
73
- end
74
-
75
- # Build toolset reminder listing all available tools
76
- #
77
- # @param chat [Agent::Chat] The chat instance
78
- # @return [String] System reminder with tool list
79
- def build_toolset_reminder(chat)
80
- tools_list = chat.tool_names
81
-
82
- reminder = "<system-reminder>\n"
83
- reminder += "Tools available: #{tools_list.join(", ")}\n\n"
84
- reminder += "Only use tools from this list. Do not attempt to use tools that are not listed here.\n"
85
- reminder += "</system-reminder>"
86
-
87
- reminder
88
- end
89
-
90
- # Check if we should inject a periodic TodoWrite reminder
91
- #
92
- # Injects a reminder if:
93
- # 1. Enough messages have passed (>= 5)
94
- # 2. TodoWrite hasn't been used in the last TODOWRITE_REMINDER_INTERVAL messages
95
- #
96
- # @param chat [Agent::Chat] The chat instance
97
- # @param last_todowrite_index [Integer, nil] Index of last TodoWrite usage
98
- # @return [Boolean] true if reminder should be injected
99
- def should_inject_todowrite_reminder?(chat, last_todowrite_index)
100
- # Need at least a few messages before reminding
101
- return false if chat.message_count < 5
102
-
103
- # Find the last message that contains TodoWrite tool usage
104
- last_todo_index = chat.find_last_message_index do |msg|
105
- msg.role == :tool && msg.content.to_s.include?("TodoWrite")
106
- end
107
-
108
- # Check if enough messages have passed since last TodoWrite
109
- if last_todo_index.nil? && last_todowrite_index.nil?
110
- # Never used TodoWrite - check if we've exceeded interval
111
- chat.message_count >= TODOWRITE_REMINDER_INTERVAL
112
- elsif last_todo_index
113
- # Recently used - don't remind
114
- false
115
- elsif last_todowrite_index
116
- # Used before - check if interval has passed
117
- chat.message_count - last_todowrite_index >= TODOWRITE_REMINDER_INTERVAL
118
- else
119
- false
120
- end
121
- end
122
-
123
- # Update the last TodoWrite index by finding it in messages
124
- #
125
- # @param chat [Agent::Chat] The chat instance
126
- # @return [Integer, nil] Index of last TodoWrite usage, or nil
127
- def find_last_todowrite_index(chat)
128
- chat.find_last_message_index do |msg|
129
- msg.role == :tool && msg.content.to_s.include?("TodoWrite")
130
- end
131
- end
132
- end
133
- end
134
- end
135
- end
136
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- module ChatHelpers
6
- # System reminder collection and injection
7
- #
8
- # Extracted from Chat to reduce class size and centralize reminder logic.
9
- module SystemReminders
10
- # Collect reminders from all plugins
11
- #
12
- # @param prompt [String] User's message
13
- # @param is_first_message [Boolean] True if first message
14
- # @return [Array<String>] Array of reminder strings
15
- def collect_plugin_reminders(prompt, is_first_message:)
16
- return [] unless @agent_name
17
-
18
- PluginRegistry.all.flat_map do |plugin|
19
- plugin.on_user_message(
20
- agent_name: @agent_name,
21
- prompt: prompt,
22
- is_first_message: is_first_message,
23
- )
24
- end.compact
25
- end
26
-
27
- # Collect all system reminders for this message
28
- #
29
- # Returns an array of reminder strings that should be injected as ephemeral content.
30
- # These are sent to the LLM but not stored in message history.
31
- #
32
- # @param prompt [String] User prompt
33
- # @param is_first [Boolean] Whether this is the first message
34
- # @return [Array<String>] Array of reminder strings
35
- def collect_system_reminders(prompt, is_first)
36
- reminders = []
37
-
38
- if is_first
39
- # Add toolset reminder on first message
40
- reminders << build_toolset_reminder
41
-
42
- # Add todo list reminder if agent has TodoWrite tool
43
- reminders << SystemReminderInjector::AFTER_FIRST_MESSAGE_REMINDER if has_tool?(:TodoWrite)
44
-
45
- # Collect plugin reminders
46
- reminders.concat(collect_plugin_reminders(prompt, is_first_message: true))
47
- else
48
- # Add periodic TodoWrite reminder if needed
49
- if has_tool?(:TodoWrite) && SystemReminderInjector.should_inject_todowrite_reminder?(self, @last_todowrite_message_index)
50
- reminders << SystemReminderInjector::TODOWRITE_PERIODIC_REMINDER
51
- @last_todowrite_message_index = SystemReminderInjector.find_last_todowrite_index(self)
52
- end
53
-
54
- # Collect plugin reminders
55
- reminders.concat(collect_plugin_reminders(prompt, is_first_message: false))
56
- end
57
-
58
- reminders
59
- end
60
-
61
- private
62
-
63
- # Build toolset reminder listing all available tools
64
- #
65
- # @return [String] System reminder with tool list
66
- def build_toolset_reminder
67
- tools_list = tool_names
68
-
69
- reminder = "<system-reminder>\n"
70
- reminder += "Tools available: #{tools_list.join(", ")}\n\n"
71
- reminder += "Only use tools from this list. Do not attempt to use tools that are not listed here.\n"
72
- reminder += "</system-reminder>"
73
-
74
- reminder
75
- end
76
- end
77
- end
78
- end
79
- end