swarm_sdk 2.7.14 → 3.0.0.alpha2

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 (185) 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/document_converters/base.rb +84 -0
  42. data/lib/swarm_sdk/v3/tools/document_converters/docx_converter.rb +120 -0
  43. data/lib/swarm_sdk/v3/tools/document_converters/pdf_converter.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/document_converters/xlsx_converter.rb +128 -0
  45. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  46. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  47. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  48. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  49. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  50. data/lib/swarm_sdk/v3/tools/read.rb +213 -0
  51. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  52. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  53. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  54. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  55. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  56. data/lib/swarm_sdk/v3.rb +145 -0
  57. metadata +88 -149
  58. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  59. data/lib/swarm_sdk/agent/builder.rb +0 -705
  60. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  61. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  62. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  63. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  64. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  65. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  66. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  67. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  68. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  69. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  70. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  71. data/lib/swarm_sdk/agent/context.rb +0 -115
  72. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  73. data/lib/swarm_sdk/agent/definition.rb +0 -588
  74. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  75. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  76. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  77. data/lib/swarm_sdk/agent_registry.rb +0 -146
  78. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  79. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  80. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  81. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  82. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  83. data/lib/swarm_sdk/config.rb +0 -368
  84. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  85. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  86. data/lib/swarm_sdk/configuration.rb +0 -165
  87. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  88. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  89. data/lib/swarm_sdk/context_compactor.rb +0 -335
  90. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  91. data/lib/swarm_sdk/context_management/context.rb +0 -328
  92. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  93. data/lib/swarm_sdk/defaults.rb +0 -251
  94. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  95. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  96. data/lib/swarm_sdk/hooks/context.rb +0 -197
  97. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  98. data/lib/swarm_sdk/hooks/error.rb +0 -29
  99. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  100. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  101. data/lib/swarm_sdk/hooks/result.rb +0 -150
  102. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  103. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  104. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  105. data/lib/swarm_sdk/log_collector.rb +0 -227
  106. data/lib/swarm_sdk/log_stream.rb +0 -127
  107. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  108. data/lib/swarm_sdk/model_aliases.json +0 -8
  109. data/lib/swarm_sdk/models.json +0 -44002
  110. data/lib/swarm_sdk/models.rb +0 -161
  111. data/lib/swarm_sdk/node_context.rb +0 -245
  112. data/lib/swarm_sdk/observer/builder.rb +0 -81
  113. data/lib/swarm_sdk/observer/config.rb +0 -45
  114. data/lib/swarm_sdk/observer/manager.rb +0 -248
  115. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  116. data/lib/swarm_sdk/permissions/config.rb +0 -239
  117. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  118. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  119. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  120. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  121. data/lib/swarm_sdk/plugin.rb +0 -309
  122. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  123. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  124. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  125. data/lib/swarm_sdk/restore_result.rb +0 -65
  126. data/lib/swarm_sdk/result.rb +0 -241
  127. data/lib/swarm_sdk/snapshot.rb +0 -156
  128. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  129. data/lib/swarm_sdk/state_restorer.rb +0 -476
  130. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  131. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  132. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  133. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  134. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  135. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  136. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  141. data/lib/swarm_sdk/swarm.rb +0 -973
  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/base.rb +0 -63
  145. data/lib/swarm_sdk/tools/bash.rb +0 -280
  146. data/lib/swarm_sdk/tools/clock.rb +0 -46
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  160. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  161. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  162. data/lib/swarm_sdk/tools/read.rb +0 -261
  163. data/lib/swarm_sdk/tools/registry.rb +0 -205
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  166. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  167. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  168. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  169. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  170. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  171. data/lib/swarm_sdk/tools/think.rb +0 -100
  172. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  173. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  174. data/lib/swarm_sdk/tools/write.rb +0 -112
  175. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  176. data/lib/swarm_sdk/utils.rb +0 -68
  177. data/lib/swarm_sdk/validation_result.rb +0 -33
  178. data/lib/swarm_sdk/version.rb +0 -5
  179. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  180. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  181. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  182. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  183. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  184. data/lib/swarm_sdk/workflow.rb +0 -589
  185. data/lib/swarm_sdk.rb +0 -721
@@ -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 == SwarmSDK.config.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,85 +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
- puts "=== inject_llm_instrumentation called ===" if ENV["DEBUG_CITATIONS"]
17
- return unless @provider
18
-
19
- puts "=== Has provider ===" if ENV["DEBUG_CITATIONS"]
20
-
21
- faraday_conn = @provider.connection&.connection
22
- return unless faraday_conn
23
-
24
- puts "=== Has faraday connection ===" if ENV["DEBUG_CITATIONS"]
25
- return if @llm_instrumentation_injected
26
-
27
- provider_name = @provider.class.name.split("::").last.downcase
28
-
29
- faraday_conn.builder.insert(
30
- 0,
31
- SwarmSDK::Agent::LLMInstrumentationMiddleware,
32
- on_request: method(:handle_llm_api_request),
33
- on_response: method(:handle_llm_api_response),
34
- provider_name: provider_name,
35
- )
36
-
37
- @llm_instrumentation_injected = true
38
-
39
- puts "=== MIDDLEWARE INJECTED ===" if ENV["DEBUG_CITATIONS"]
40
- RubyLLM.logger.debug("SwarmSDK: Injected LLM instrumentation middleware for agent #{@agent_name}")
41
- rescue StandardError => e
42
- puts "=== MIDDLEWARE INJECTION ERROR: #{e.message} ===" if ENV["DEBUG_CITATIONS"]
43
- LogStream.emit_error(e, source: "instrumentation", context: "inject_middleware", agent: @agent_name)
44
- RubyLLM.logger.debug("SwarmSDK: Failed to inject LLM instrumentation: #{e.message}")
45
- end
46
-
47
- # Handle LLM API request event
48
- #
49
- # @param data [Hash] Request data from middleware
50
- def handle_llm_api_request(data)
51
- return unless LogStream.emitter
52
-
53
- LogStream.emit(
54
- type: "llm_api_request",
55
- agent: @agent_name,
56
- swarm_id: @agent_context&.swarm_id,
57
- parent_swarm_id: @agent_context&.parent_swarm_id,
58
- **data,
59
- )
60
- rescue StandardError => e
61
- LogStream.emit_error(e, source: "instrumentation", context: "emit_llm_api_request", agent: @agent_name)
62
- RubyLLM.logger.debug("SwarmSDK: Error emitting llm_api_request event: #{e.message}")
63
- end
64
-
65
- # Handle LLM API response event
66
- #
67
- # @param data [Hash] Response data from middleware
68
- def handle_llm_api_response(data)
69
- return unless LogStream.emitter
70
-
71
- LogStream.emit(
72
- type: "llm_api_response",
73
- agent: @agent_name,
74
- swarm_id: @agent_context&.swarm_id,
75
- parent_swarm_id: @agent_context&.parent_swarm_id,
76
- **data,
77
- )
78
- rescue StandardError => e
79
- LogStream.emit_error(e, source: "instrumentation", context: "emit_llm_api_response", agent: @agent_name)
80
- RubyLLM.logger.debug("SwarmSDK: Error emitting llm_api_response event: #{e.message}")
81
- end
82
- end
83
- end
84
- end
85
- end