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,480 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- module ChatHelpers
6
- # Integrates SwarmSDK's hook system with Agent::Chat
7
- #
8
- # Responsibilities:
9
- # - Setup hook system (registry, executor, agent hooks)
10
- # - Provide trigger methods for all hook events
11
- # - Wrap ask() to inject user_prompt hooks
12
- # - Handle hook results (halt, replace, continue, reprompt)
13
- #
14
- # This module is included in Agent::Chat and provides methods for triggering hooks.
15
- # It overrides ask() to inject user_prompt hooks, but does NOT override
16
- # handle_tool_calls (that's handled in Agent::Chat with explicit hook calls).
17
- module HookIntegration
18
- # Expose hook system components for ContextTracker
19
- attr_reader :hook_executor, :hook_swarm, :hook_agent_hooks
20
-
21
- # Setup the hook system for this agent chat
22
- #
23
- # This must be called after setup_context and before the first ask/complete.
24
- # It wires up the hook system to trigger at the right times.
25
- #
26
- # @param registry [Hooks::Registry] Shared registry for named hooks and swarm defaults
27
- # @param agent_definition [Agent::Definition] Agent configuration with hooks
28
- # @param swarm [Swarm, nil] Reference to swarm for context
29
- # @return [void]
30
- def setup_hooks(registry:, agent_definition:, swarm: nil)
31
- @hook_registry = registry
32
- @hook_swarm = swarm
33
- @hook_executor = Hooks::Executor.new(registry, logger: RubyLLM.logger)
34
-
35
- # Extract agent hooks based on format
36
- hooks = agent_definition.hooks || {}
37
-
38
- # Check if hooks are pre-parsed HookDefinition objects (from DSL)
39
- # or raw YAML hash (to be processed by Hooks::Adapter in pass_5)
40
- @hook_agent_hooks = if hooks.is_a?(Hash) && hooks.values.all? { |v| v.is_a?(Array) && v.all? { |item| item.is_a?(Hooks::Definition) } }
41
- # DSL hooks - already parsed, use them
42
- hooks
43
- else
44
- # YAML hooks - raw hash, will be processed in pass_5 by Hooks::Adapter
45
- # For now, use empty hash (pass_5 will add them later)
46
- {}
47
- end
48
- end
49
-
50
- # Add a hook programmatically at runtime
51
- #
52
- # This allows agents to add hooks dynamically, which is useful for
53
- # implementing adaptive behavior or runtime monitoring.
54
- #
55
- # @param event [Symbol] Event type (e.g., :pre_tool_use)
56
- # @param matcher [String, Regexp, nil] Optional regex pattern for tool names
57
- # @param priority [Integer] Execution priority (higher = earlier)
58
- # @param block [Proc] Hook implementation
59
- def add_hook(event, matcher: nil, priority: 0, &block)
60
- raise ArgumentError, "Hooks not set up. Call setup_hooks first." unless @hook_executor
61
-
62
- definition = Hooks::Definition.new(
63
- event: event,
64
- matcher: matcher,
65
- priority: priority,
66
- proc: block,
67
- )
68
-
69
- @hook_agent_hooks[event] ||= []
70
- @hook_agent_hooks[event] << definition
71
- @hook_agent_hooks[event].sort_by! { |cb| -cb.priority }
72
- end
73
-
74
- # NOTE: The ask() method override has been removed.
75
- #
76
- # In the new wrapper-based architecture, Agent::Chat#ask handles:
77
- # 1. System reminder injection
78
- # 2. User prompt hooks via trigger_user_prompt
79
- # 3. Global semaphore acquisition
80
- # 4. Delegation to RubyLLM::Chat
81
- #
82
- # The hook integration is now done directly in Agent::Chat#ask rather than
83
- # through module override, since there's no inheritance chain to call super on.
84
-
85
- # Override check_context_warnings to trigger our hook system
86
- #
87
- # This wraps the default context warning behavior to also trigger hooks.
88
- # Unified implementation that:
89
- # 1. Emits context_threshold_hit events (for snapshot reconstruction)
90
- # 2. Optionally triggers automatic compression at 60% (if no custom handler)
91
- # 3. Emits context_limit_warning events (backward compatibility)
92
- # 4. Triggers user-defined context_warning hooks
93
- def check_context_warnings
94
- return unless respond_to?(:context_usage_percentage)
95
-
96
- current_percentage = context_usage_percentage
97
-
98
- Context::CONTEXT_WARNING_THRESHOLDS.each do |threshold|
99
- # Only warn once per threshold
100
- next if @agent_context.warning_threshold_hit?(threshold)
101
- next if current_percentage < threshold
102
-
103
- # Mark threshold as hit
104
- @agent_context.hit_warning_threshold?(threshold)
105
-
106
- # Emit context_threshold_hit event (for snapshot reconstruction) - CRITICAL
107
- LogStream.emit(
108
- type: "context_threshold_hit",
109
- agent: @agent_context.name,
110
- threshold: threshold,
111
- current_usage_percentage: current_percentage.round(2),
112
- )
113
-
114
- # Check if user has defined custom handler for context_warning
115
- # Custom handlers take responsibility for managing context at this threshold
116
- has_custom_handler = (@hook_agent_hooks[:context_warning] || []).any?
117
-
118
- # Trigger automatic compression at 60% ONLY if no custom handler
119
- compression_triggered = false
120
- if threshold == Context::COMPRESSION_THRESHOLD && !has_custom_handler
121
- compressed_count = apply_automatic_compression
122
- compression_triggered = compressed_count > 0
123
- end
124
-
125
- # Emit legacy context_limit_warning for backwards compatibility
126
- LogStream.emit(
127
- type: "context_limit_warning",
128
- agent: @agent_context.name,
129
- model: model_id,
130
- threshold: "#{threshold}%",
131
- current_usage: "#{current_percentage}%",
132
- tokens_used: cumulative_total_tokens,
133
- tokens_remaining: tokens_remaining,
134
- context_limit: context_limit,
135
- metadata: @agent_context.metadata,
136
- compression_triggered: compression_triggered,
137
- )
138
-
139
- # Trigger hook system (user-defined handlers)
140
- trigger_context_warning(threshold, current_percentage) if @hook_executor
141
- end
142
- end
143
-
144
- # Trigger pre_tool_use hooks
145
- #
146
- # Should be called by Agent::Chat before tool execution.
147
- # Returns a hash indicating whether to proceed and any custom result.
148
- #
149
- # @param tool_call [RubyLLM::ToolCall] Tool call from LLM
150
- # @return [Hash] { proceed: true/false, custom_result: result (if any) }
151
- def trigger_pre_tool_use(tool_call)
152
- return { proceed: true } unless @hook_executor
153
-
154
- context = build_hook_context(
155
- event: :pre_tool_use,
156
- tool_call: wrap_tool_call_to_hooks(tool_call),
157
- )
158
-
159
- agent_hooks = @hook_agent_hooks[:pre_tool_use] || []
160
-
161
- result = @hook_executor.execute_safe(
162
- event: :pre_tool_use,
163
- context: context,
164
- callbacks: agent_hooks,
165
- )
166
-
167
- # Return custom result if hook provides one
168
- if result.replace?
169
- { proceed: false, custom_result: result.value }
170
- elsif result.halt?
171
- { proceed: false, custom_result: result.value || "Tool execution blocked by hook" }
172
- elsif result.finish_agent?
173
- # Finish agent execution immediately with this message
174
- { proceed: false, finish_agent: true, custom_result: result.value }
175
- elsif result.finish_swarm?
176
- # Finish entire swarm execution immediately with this message
177
- { proceed: false, finish_swarm: true, custom_result: result.value }
178
- else
179
- { proceed: true }
180
- end
181
- end
182
-
183
- # Trigger post_tool_use hooks
184
- #
185
- # Should be called by Agent::Chat after tool execution.
186
- # Returns modified result if hook replaces it, or a special marker for finish actions.
187
- #
188
- # @param result [String, Object] Tool execution result
189
- # @param tool_call [RubyLLM::ToolCall] Tool call object with full context
190
- # @return [Object, Hash] Modified result if hook replaces it, hash with :finish_agent or :finish_swarm if finishing, otherwise original result
191
- def trigger_post_tool_use(result, tool_call:)
192
- return result unless @hook_executor
193
-
194
- # Extract tracking digest for Read/MemoryRead tools
195
- metadata_with_digest = extract_tool_tracking_digest(tool_call, result)
196
-
197
- context = build_hook_context(
198
- event: :post_tool_use,
199
- tool_result: wrap_tool_result(tool_call.id, tool_call.name, result),
200
- metadata: metadata_with_digest,
201
- )
202
-
203
- agent_hooks = @hook_agent_hooks[:post_tool_use] || []
204
-
205
- hook_result = @hook_executor.execute_safe(
206
- event: :post_tool_use,
207
- context: context,
208
- callbacks: agent_hooks,
209
- )
210
-
211
- # Return modified result or finish markers
212
- if hook_result.replace?
213
- hook_result.value
214
- elsif hook_result.finish_agent?
215
- { __finish_agent__: true, message: hook_result.value }
216
- elsif hook_result.finish_swarm?
217
- { __finish_swarm__: true, message: hook_result.value }
218
- else
219
- result
220
- end
221
- end
222
-
223
- private
224
-
225
- # Apply automatic message compression when context threshold is hit
226
- #
227
- # Called when context usage crosses 60% threshold and no custom handler exists.
228
- # Compresses old tool results to save context window space while preserving accuracy.
229
- #
230
- # @return [Integer] Number of messages compressed (0 if compression not applied)
231
- def apply_automatic_compression
232
- return 0 unless respond_to?(:context_manager) && respond_to?(:messages)
233
-
234
- # Calculate tokens before compression
235
- tokens_before = cumulative_total_tokens
236
-
237
- # Get compressed messages from ContextManager
238
- compressed = context_manager.auto_compress_on_threshold(messages, keep_recent: 10)
239
-
240
- # Count how many messages were actually compressed
241
- messages_compressed = compressed.count do |msg|
242
- msg.content.to_s.include?("[truncated for context management]")
243
- end
244
-
245
- # Replace messages using proper abstraction
246
- replace_messages(compressed)
247
-
248
- # Log compression event
249
- LogStream.emit(
250
- type: "context_compression",
251
- agent: @agent_context.name,
252
- total_messages: message_count,
253
- messages_compressed: messages_compressed,
254
- tokens_before: tokens_before,
255
- current_usage: "#{context_usage_percentage}%",
256
- compression_strategy: "progressive_tool_result_compression",
257
- keep_recent: 10,
258
- triggered_by: "auto_compression_threshold",
259
- ) if LogStream.enabled?
260
-
261
- messages_compressed
262
- end
263
-
264
- # Trigger context_warning hooks
265
- #
266
- # Hooks have access to the chat instance via metadata[:chat]
267
- # to access and manipulate the messages array.
268
- #
269
- # @param threshold [Integer] Warning threshold percentage
270
- # @param current_usage [Float] Current usage percentage
271
- # @return [void]
272
- def trigger_context_warning(threshold, current_usage)
273
- return unless @hook_executor
274
-
275
- context = build_hook_context(
276
- event: :context_warning,
277
- metadata: {
278
- chat: self, # Provide access to chat instance (for messages array)
279
- threshold: threshold,
280
- percentage: current_usage,
281
- tokens_used: cumulative_total_tokens,
282
- tokens_remaining: tokens_remaining,
283
- context_limit: context_limit,
284
- },
285
- )
286
-
287
- agent_hooks = @hook_agent_hooks[:context_warning] || []
288
-
289
- @hook_executor.execute_safe(
290
- event: :context_warning,
291
- context: context,
292
- callbacks: agent_hooks,
293
- )
294
- end
295
-
296
- # Trigger user_prompt hooks
297
- #
298
- # This fires before sending a user message to the LLM.
299
- # Can halt execution or append hook stdout to prompt.
300
- #
301
- # @param prompt [String] User's message/prompt
302
- # @param source [String] Source of the prompt ("user" or "delegation")
303
- # @return [Hash] { halted: bool, halt_message: String, modified_prompt: String }
304
- def trigger_user_prompt(prompt, source: "user")
305
- return { halted: false, modified_prompt: prompt } unless @hook_executor
306
-
307
- # Get tool names without delegation tools using proper abstraction
308
- actual_tools = if respond_to?(:non_delegation_tool_names) && @agent_context
309
- non_delegation_tool_names
310
- else
311
- []
312
- end
313
-
314
- # Extract agent names from delegation tool names
315
- delegate_agents = if @agent_context&.delegation_tools
316
- @agent_context.delegation_tools.map { |tool_name| @context_tracker.extract_delegate_agent_name(tool_name) }
317
- else
318
- []
319
- end
320
-
321
- context = build_hook_context(
322
- event: :user_prompt,
323
- metadata: {
324
- prompt: prompt,
325
- message_count: message_count,
326
- model: model_id,
327
- provider: model_provider,
328
- tools: actual_tools,
329
- delegates_to: delegate_agents,
330
- source: source,
331
- timestamp: Time.now.utc.iso8601,
332
- },
333
- )
334
-
335
- agent_hooks = @hook_agent_hooks[:user_prompt] || []
336
-
337
- result = @hook_executor.execute_safe(
338
- event: :user_prompt,
339
- context: context,
340
- callbacks: agent_hooks,
341
- )
342
-
343
- # Handle hook result
344
- if result.halt?
345
- # Hook blocked execution
346
- { halted: true, halt_message: result.value }
347
- elsif result.replace?
348
- # Hook provided stdout to append to prompt (exit code 0)
349
- modified_prompt = "#{prompt}\n\n<hook-context>\n#{result.value}\n</hook-context>"
350
- { halted: false, modified_prompt: modified_prompt }
351
- else
352
- # Normal continue
353
- { halted: false, modified_prompt: prompt }
354
- end
355
- end
356
-
357
- # Build a hook context object
358
- #
359
- # @param event [Symbol] Event type
360
- # @param tool_call [Hooks::ToolCall, nil] Tool call object
361
- # @param tool_result [Hooks::ToolResult, nil] Tool result object
362
- # @param metadata [Hash] Additional metadata
363
- # @return [Hooks::Context] Context object
364
- def build_hook_context(event:, tool_call: nil, tool_result: nil, metadata: {})
365
- Hooks::Context.new(
366
- event: event,
367
- agent_name: @agent_context&.name || "unknown",
368
- agent_definition: nil, # Could store this in setup_hooks if needed
369
- swarm: @hook_swarm,
370
- tool_call: tool_call,
371
- tool_result: tool_result,
372
- metadata: metadata,
373
- )
374
- end
375
-
376
- # Wrap a RubyLLM tool call in our Hooks::ToolCall value object
377
- #
378
- # @param tool_call [RubyLLM::ToolCall] RubyLLM tool call
379
- # @return [Hooks::ToolCall] Our wrapped tool call
380
- def wrap_tool_call_to_hooks(tool_call)
381
- Hooks::ToolCall.new(
382
- id: tool_call.id,
383
- name: tool_call.name,
384
- parameters: tool_call.arguments,
385
- )
386
- end
387
-
388
- # Extract tracking digest for Read/MemoryRead tools
389
- #
390
- # Queries the appropriate tracker after tool execution to get the digest
391
- # that was calculated and stored during the read operation.
392
- #
393
- # @param tool_call [RubyLLM::ToolCall] Tool call with arguments
394
- # @param result [Object] Tool execution result (to check for errors)
395
- # @return [Hash] Metadata hash with digest if applicable
396
- def extract_tool_tracking_digest(tool_call, result)
397
- # Only add digest for successful Read/MemoryRead tool calls
398
- return {} if result.is_a?(StandardError)
399
- return {} unless ["Read", "MemoryRead"].include?(tool_call.name)
400
-
401
- # Extract path from arguments
402
- path = case tool_call.name
403
- when "Read"
404
- tool_call.arguments[:file_path] || tool_call.arguments["file_path"]
405
- when "MemoryRead"
406
- tool_call.arguments[:file_path] || tool_call.arguments["file_path"]
407
- end
408
-
409
- return {} unless path
410
-
411
- # Query tracker for digest
412
- digest = case tool_call.name
413
- when "Read"
414
- Tools::Stores::ReadTracker.get_read_files(@agent_context.name)[File.expand_path(path)]
415
- else
416
- # Query registered plugins for digest (e.g., MemoryRead from SwarmMemory plugin)
417
- query_plugin_for_digest(tool_call.name, path)
418
- end
419
-
420
- digest ? { read_digest: digest, read_path: path } : {}
421
- end
422
-
423
- # Query registered plugins for a tool result digest
424
- #
425
- # This allows plugins to provide digest tracking for their own tools
426
- # (e.g., MemoryRead tracking in SwarmMemory plugin).
427
- #
428
- # @param tool_name [String] Name of the tool
429
- # @param path [String] Path or identifier of the resource
430
- # @return [String, nil] Digest from first plugin that responds, or nil
431
- def query_plugin_for_digest(tool_name, path)
432
- return unless @agent_context
433
-
434
- PluginRegistry.all.each do |plugin|
435
- digest = plugin.get_tool_result_digest(
436
- agent_name: @agent_context.name,
437
- tool_name: tool_name,
438
- path: path,
439
- )
440
- return digest if digest
441
- end
442
-
443
- nil
444
- end
445
-
446
- # Wrap a tool result in our Hooks::ToolResult value object
447
- #
448
- # @param tool_call_id [String] Tool call ID
449
- # @param tool_name [String] Tool name
450
- # @param result [Object] Tool execution result
451
- # @return [Hooks::ToolResult] Our wrapped result
452
- def wrap_tool_result(tool_call_id, tool_name, result)
453
- success = !result.is_a?(StandardError)
454
- error = result.is_a?(StandardError) ? result.message : nil
455
-
456
- Hooks::ToolResult.new(
457
- tool_call_id: tool_call_id,
458
- tool_name: tool_name,
459
- content: success ? result : nil,
460
- success: success,
461
- error: error,
462
- )
463
- end
464
-
465
- # Check if a tool call is a delegation tool
466
- #
467
- # Delegation tools fire their own pre_delegation/post_delegation events
468
- # and should NOT fire pre_tool_use/post_tool_use events.
469
- #
470
- # @param tool_call [RubyLLM::ToolCall] Tool call to check
471
- # @return [Boolean] true if this is a delegation tool
472
- def delegation_tool_call?(tool_call)
473
- return false unless @agent_context
474
-
475
- @agent_context.delegation_tool?(tool_call.name)
476
- end
477
- end
478
- end
479
- end
480
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- module ChatHelpers
6
- # LLM instrumentation for API request/response logging
7
- #
8
- # Extracted from Chat to reduce class size and centralize observability logic.
9
- module Instrumentation
10
- private
11
-
12
- # Inject LLM instrumentation middleware for API request/response logging
13
- #
14
- # @return [void]
15
- def inject_llm_instrumentation
16
- return unless @provider
17
-
18
- faraday_conn = @provider.connection&.connection
19
- return unless faraday_conn
20
- return if @llm_instrumentation_injected
21
-
22
- provider_name = @provider.class.name.split("::").last.downcase
23
-
24
- faraday_conn.builder.insert(
25
- 0,
26
- SwarmSDK::Agent::LLMInstrumentationMiddleware,
27
- on_request: method(:handle_llm_api_request),
28
- on_response: method(:handle_llm_api_response),
29
- provider_name: provider_name,
30
- )
31
-
32
- @llm_instrumentation_injected = true
33
-
34
- RubyLLM.logger.debug("SwarmSDK: Injected LLM instrumentation middleware for agent #{@agent_name}")
35
- rescue StandardError => e
36
- LogStream.emit_error(e, source: "instrumentation", context: "inject_middleware", agent: @agent_name)
37
- RubyLLM.logger.debug("SwarmSDK: Failed to inject LLM instrumentation: #{e.message}")
38
- end
39
-
40
- # Handle LLM API request event
41
- #
42
- # @param data [Hash] Request data from middleware
43
- def handle_llm_api_request(data)
44
- return unless LogStream.emitter
45
-
46
- LogStream.emit(
47
- type: "llm_api_request",
48
- agent: @agent_name,
49
- swarm_id: @agent_context&.swarm_id,
50
- parent_swarm_id: @agent_context&.parent_swarm_id,
51
- **data,
52
- )
53
- rescue StandardError => e
54
- LogStream.emit_error(e, source: "instrumentation", context: "emit_llm_api_request", agent: @agent_name)
55
- RubyLLM.logger.debug("SwarmSDK: Error emitting llm_api_request event: #{e.message}")
56
- end
57
-
58
- # Handle LLM API response event
59
- #
60
- # @param data [Hash] Response data from middleware
61
- def handle_llm_api_response(data)
62
- return unless LogStream.emitter
63
-
64
- LogStream.emit(
65
- type: "llm_api_response",
66
- agent: @agent_name,
67
- swarm_id: @agent_context&.swarm_id,
68
- parent_swarm_id: @agent_context&.parent_swarm_id,
69
- **data,
70
- )
71
- rescue StandardError => e
72
- LogStream.emit_error(e, source: "instrumentation", context: "emit_llm_api_response", agent: @agent_name)
73
- RubyLLM.logger.debug("SwarmSDK: Error emitting llm_api_response event: #{e.message}")
74
- end
75
- end
76
- end
77
- end
78
- end