swarm_sdk 2.7.13 → 3.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
  4. data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
  5. data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
  6. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  7. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  8. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  9. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  10. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  11. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  12. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  13. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  14. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  15. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  16. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  17. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  18. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  19. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  20. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  24. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  25. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  26. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  27. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  28. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  29. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  30. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  31. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  32. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  33. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  34. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  35. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  36. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  37. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  38. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  39. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  40. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  41. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  42. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  43. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  45. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  46. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  47. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  48. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  49. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  50. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  51. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  52. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  53. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  54. data/lib/swarm_sdk/v3.rb +145 -0
  55. metadata +84 -148
  56. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  57. data/lib/swarm_sdk/agent/builder.rb +0 -680
  58. data/lib/swarm_sdk/agent/chat.rb +0 -1432
  59. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  60. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  61. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  62. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  63. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  64. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  65. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  66. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  67. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  68. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  69. data/lib/swarm_sdk/agent/context.rb +0 -115
  70. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  71. data/lib/swarm_sdk/agent/definition.rb +0 -581
  72. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  73. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  74. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  75. data/lib/swarm_sdk/agent_registry.rb +0 -146
  76. data/lib/swarm_sdk/builders/base_builder.rb +0 -553
  77. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  78. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  79. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  80. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  81. data/lib/swarm_sdk/config.rb +0 -367
  82. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  83. data/lib/swarm_sdk/configuration/translator.rb +0 -283
  84. data/lib/swarm_sdk/configuration.rb +0 -165
  85. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  86. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  87. data/lib/swarm_sdk/context_compactor.rb +0 -335
  88. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  89. data/lib/swarm_sdk/context_management/context.rb +0 -328
  90. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  91. data/lib/swarm_sdk/defaults.rb +0 -251
  92. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  93. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  94. data/lib/swarm_sdk/hooks/context.rb +0 -197
  95. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  96. data/lib/swarm_sdk/hooks/error.rb +0 -29
  97. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  98. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  99. data/lib/swarm_sdk/hooks/result.rb +0 -150
  100. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  101. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  102. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  103. data/lib/swarm_sdk/log_collector.rb +0 -227
  104. data/lib/swarm_sdk/log_stream.rb +0 -127
  105. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  106. data/lib/swarm_sdk/model_aliases.json +0 -8
  107. data/lib/swarm_sdk/models.json +0 -44002
  108. data/lib/swarm_sdk/models.rb +0 -161
  109. data/lib/swarm_sdk/node_context.rb +0 -245
  110. data/lib/swarm_sdk/observer/builder.rb +0 -81
  111. data/lib/swarm_sdk/observer/config.rb +0 -45
  112. data/lib/swarm_sdk/observer/manager.rb +0 -236
  113. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  114. data/lib/swarm_sdk/permissions/config.rb +0 -239
  115. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  116. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  117. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  118. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  119. data/lib/swarm_sdk/plugin.rb +0 -309
  120. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  121. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  122. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  123. data/lib/swarm_sdk/restore_result.rb +0 -65
  124. data/lib/swarm_sdk/result.rb +0 -212
  125. data/lib/swarm_sdk/snapshot.rb +0 -156
  126. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  127. data/lib/swarm_sdk/state_restorer.rb +0 -476
  128. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  129. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  130. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
  131. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  132. data/lib/swarm_sdk/swarm/executor.rb +0 -290
  133. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
  134. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  135. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
  136. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
  137. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  138. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  139. data/lib/swarm_sdk/swarm.rb +0 -843
  140. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  141. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  142. data/lib/swarm_sdk/tools/base.rb +0 -63
  143. data/lib/swarm_sdk/tools/bash.rb +0 -280
  144. data/lib/swarm_sdk/tools/clock.rb +0 -46
  145. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  146. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  147. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  148. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  149. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  150. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  151. data/lib/swarm_sdk/tools/edit.rb +0 -145
  152. data/lib/swarm_sdk/tools/glob.rb +0 -166
  153. data/lib/swarm_sdk/tools/grep.rb +0 -235
  154. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  155. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  156. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  157. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  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 -273
  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 -100
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  182. data/lib/swarm_sdk/workflow.rb +0 -589
  183. data/lib/swarm_sdk.rb +0 -718
@@ -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