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.
- checksums.yaml +4 -4
- data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
- data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
- data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
- data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
- data/lib/swarm_sdk/v3/agent.rb +1165 -0
- data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
- data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
- data/lib/swarm_sdk/v3/configuration.rb +490 -0
- data/lib/swarm_sdk/v3/debug_log.rb +86 -0
- data/lib/swarm_sdk/v3/event_stream.rb +130 -0
- data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
- data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
- data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
- data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
- data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
- data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
- data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
- data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
- data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
- data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
- data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
- data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
- data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
- data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
- data/lib/swarm_sdk/v3/memory/card.rb +206 -0
- data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
- data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
- data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
- data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
- data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
- data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
- data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
- data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
- data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
- data/lib/swarm_sdk/v3/memory/store.rb +489 -0
- data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
- data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
- data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
- data/lib/swarm_sdk/v3/tools/base.rb +80 -0
- data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
- data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
- data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
- data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
- data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
- data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
- data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
- data/lib/swarm_sdk/v3/tools/read.rb +181 -0
- data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
- data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
- data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
- data/lib/swarm_sdk/v3/tools/think.rb +88 -0
- data/lib/swarm_sdk/v3/tools/write.rb +87 -0
- data/lib/swarm_sdk/v3.rb +145 -0
- metadata +84 -148
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
- data/lib/swarm_sdk/agent/builder.rb +0 -680
- data/lib/swarm_sdk/agent/chat.rb +0 -1432
- data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
- data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
- data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
- data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
- data/lib/swarm_sdk/agent/context.rb +0 -115
- data/lib/swarm_sdk/agent/context_manager.rb +0 -315
- data/lib/swarm_sdk/agent/definition.rb +0 -581
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
- data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
- data/lib/swarm_sdk/agent_registry.rb +0 -146
- data/lib/swarm_sdk/builders/base_builder.rb +0 -553
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
- data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
- data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
- data/lib/swarm_sdk/concerns/validatable.rb +0 -55
- data/lib/swarm_sdk/config.rb +0 -367
- data/lib/swarm_sdk/configuration/parser.rb +0 -397
- data/lib/swarm_sdk/configuration/translator.rb +0 -283
- data/lib/swarm_sdk/configuration.rb +0 -165
- data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
- data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
- data/lib/swarm_sdk/context_compactor.rb +0 -335
- data/lib/swarm_sdk/context_management/builder.rb +0 -128
- data/lib/swarm_sdk/context_management/context.rb +0 -328
- data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
- data/lib/swarm_sdk/defaults.rb +0 -251
- data/lib/swarm_sdk/events_to_messages.rb +0 -199
- data/lib/swarm_sdk/hooks/adapter.rb +0 -359
- data/lib/swarm_sdk/hooks/context.rb +0 -197
- data/lib/swarm_sdk/hooks/definition.rb +0 -80
- data/lib/swarm_sdk/hooks/error.rb +0 -29
- data/lib/swarm_sdk/hooks/executor.rb +0 -146
- data/lib/swarm_sdk/hooks/registry.rb +0 -147
- data/lib/swarm_sdk/hooks/result.rb +0 -150
- data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
- data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
- data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
- data/lib/swarm_sdk/log_collector.rb +0 -227
- data/lib/swarm_sdk/log_stream.rb +0 -127
- data/lib/swarm_sdk/markdown_parser.rb +0 -75
- data/lib/swarm_sdk/model_aliases.json +0 -8
- data/lib/swarm_sdk/models.json +0 -44002
- data/lib/swarm_sdk/models.rb +0 -161
- data/lib/swarm_sdk/node_context.rb +0 -245
- data/lib/swarm_sdk/observer/builder.rb +0 -81
- data/lib/swarm_sdk/observer/config.rb +0 -45
- data/lib/swarm_sdk/observer/manager.rb +0 -236
- data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
- data/lib/swarm_sdk/permissions/config.rb +0 -239
- data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
- data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
- data/lib/swarm_sdk/permissions/validator.rb +0 -173
- data/lib/swarm_sdk/permissions_builder.rb +0 -122
- data/lib/swarm_sdk/plugin.rb +0 -309
- data/lib/swarm_sdk/plugin_registry.rb +0 -101
- data/lib/swarm_sdk/proc_helpers.rb +0 -53
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
- data/lib/swarm_sdk/restore_result.rb +0 -65
- data/lib/swarm_sdk/result.rb +0 -212
- data/lib/swarm_sdk/snapshot.rb +0 -156
- data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
- data/lib/swarm_sdk/state_restorer.rb +0 -476
- data/lib/swarm_sdk/state_snapshot.rb +0 -334
- data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
- data/lib/swarm_sdk/swarm/builder.rb +0 -256
- data/lib/swarm_sdk/swarm/executor.rb +0 -290
- data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
- data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
- data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
- data/lib/swarm_sdk/swarm.rb +0 -843
- data/lib/swarm_sdk/swarm_loader.rb +0 -145
- data/lib/swarm_sdk/swarm_registry.rb +0 -136
- data/lib/swarm_sdk/tools/base.rb +0 -63
- data/lib/swarm_sdk/tools/bash.rb +0 -280
- data/lib/swarm_sdk/tools/clock.rb +0 -46
- data/lib/swarm_sdk/tools/delegate.rb +0 -389
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
- data/lib/swarm_sdk/tools/edit.rb +0 -145
- data/lib/swarm_sdk/tools/glob.rb +0 -166
- data/lib/swarm_sdk/tools/grep.rb +0 -235
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
- data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
- data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
- data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
- data/lib/swarm_sdk/tools/read.rb +0 -261
- data/lib/swarm_sdk/tools/registry.rb +0 -205
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
- data/lib/swarm_sdk/tools/think.rb +0 -100
- data/lib/swarm_sdk/tools/todo_write.rb +0 -237
- data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
- data/lib/swarm_sdk/tools/write.rb +0 -112
- data/lib/swarm_sdk/transcript_builder.rb +0 -278
- data/lib/swarm_sdk/utils.rb +0 -68
- data/lib/swarm_sdk/validation_result.rb +0 -33
- data/lib/swarm_sdk/version.rb +0 -5
- data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
- data/lib/swarm_sdk/workflow/builder.rb +0 -227
- data/lib/swarm_sdk/workflow/executor.rb +0 -497
- data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
- data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
- data/lib/swarm_sdk/workflow.rb +0 -589
- data/lib/swarm_sdk.rb +0 -718
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
module ContextManagement
|
|
5
|
-
# Rich context wrapper for context management handlers
|
|
6
|
-
#
|
|
7
|
-
# Provides a clean, developer-friendly API for manipulating the conversation
|
|
8
|
-
# context when warning thresholds are triggered. Wraps the lower-level
|
|
9
|
-
# Hooks::Context with message manipulation helpers.
|
|
10
|
-
#
|
|
11
|
-
# @example Basic usage in handler
|
|
12
|
-
# on :warning_60 do |ctx|
|
|
13
|
-
# ctx.compress_tool_results(keep_recent: 10)
|
|
14
|
-
# end
|
|
15
|
-
#
|
|
16
|
-
# @example Advanced usage with metrics
|
|
17
|
-
# on :warning_80 do |ctx|
|
|
18
|
-
# if ctx.usage_percentage > 85
|
|
19
|
-
# ctx.prune_old_messages(keep_recent: 10)
|
|
20
|
-
# ctx.log_action("aggressive_pruning", remaining: ctx.tokens_remaining)
|
|
21
|
-
# else
|
|
22
|
-
# ctx.compress_tool_results(keep_recent: 5, truncate_to: 100)
|
|
23
|
-
# end
|
|
24
|
-
# end
|
|
25
|
-
class Context
|
|
26
|
-
# Create a new context wrapper
|
|
27
|
-
#
|
|
28
|
-
# @param hooks_context [Hooks::Context] Lower-level hook context with metadata
|
|
29
|
-
def initialize(hooks_context)
|
|
30
|
-
@hooks_context = hooks_context
|
|
31
|
-
@chat = hooks_context.metadata[:chat]
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# --- Context Metrics ---
|
|
35
|
-
|
|
36
|
-
# Current context usage percentage
|
|
37
|
-
#
|
|
38
|
-
# @return [Float] Usage percentage (0.0 to 100.0)
|
|
39
|
-
#
|
|
40
|
-
# @example
|
|
41
|
-
# if ctx.usage_percentage > 85
|
|
42
|
-
# ctx.prune_old_messages(keep_recent: 10)
|
|
43
|
-
# end
|
|
44
|
-
def usage_percentage
|
|
45
|
-
@hooks_context.metadata[:percentage]
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Threshold that triggered this handler
|
|
49
|
-
#
|
|
50
|
-
# @return [Integer] Threshold (60, 80, or 90)
|
|
51
|
-
#
|
|
52
|
-
# @example
|
|
53
|
-
# ctx.log_action("threshold_hit", threshold: ctx.threshold)
|
|
54
|
-
def threshold
|
|
55
|
-
@hooks_context.metadata[:threshold]
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Total tokens used so far
|
|
59
|
-
#
|
|
60
|
-
# @return [Integer] Token count
|
|
61
|
-
#
|
|
62
|
-
# @example
|
|
63
|
-
# ctx.log_action("usage", tokens: ctx.tokens_used)
|
|
64
|
-
def tokens_used
|
|
65
|
-
@hooks_context.metadata[:tokens_used]
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Tokens remaining in context window
|
|
69
|
-
#
|
|
70
|
-
# @return [Integer] Token count
|
|
71
|
-
#
|
|
72
|
-
# @example
|
|
73
|
-
# if ctx.tokens_remaining < 10000
|
|
74
|
-
# ctx.prune_old_messages(keep_recent: 5)
|
|
75
|
-
# end
|
|
76
|
-
def tokens_remaining
|
|
77
|
-
@hooks_context.metadata[:tokens_remaining]
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Total context window size
|
|
81
|
-
#
|
|
82
|
-
# @return [Integer] Token count
|
|
83
|
-
#
|
|
84
|
-
# @example
|
|
85
|
-
# buffer = ctx.context_limit * 0.1 # 10% buffer
|
|
86
|
-
def context_limit
|
|
87
|
-
@hooks_context.metadata[:context_limit]
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Agent name
|
|
91
|
-
#
|
|
92
|
-
# @return [Symbol] Agent identifier
|
|
93
|
-
#
|
|
94
|
-
# @example
|
|
95
|
-
# ctx.log_action("agent_context", agent: ctx.agent_name)
|
|
96
|
-
def agent_name
|
|
97
|
-
@hooks_context.agent_name
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# --- Message Access ---
|
|
101
|
-
|
|
102
|
-
# Get all messages (copy for manipulation)
|
|
103
|
-
#
|
|
104
|
-
# @return [Array<RubyLLM::Message>] Message array
|
|
105
|
-
#
|
|
106
|
-
# @example
|
|
107
|
-
# ctx.messages.each do |msg|
|
|
108
|
-
# puts "#{msg.role}: #{msg.content.length} chars"
|
|
109
|
-
# end
|
|
110
|
-
def messages
|
|
111
|
-
@chat.messages
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Number of messages
|
|
115
|
-
#
|
|
116
|
-
# @return [Integer] Message count
|
|
117
|
-
#
|
|
118
|
-
# @example
|
|
119
|
-
# if ctx.message_count > 100
|
|
120
|
-
# ctx.prune_old_messages(keep_recent: 50)
|
|
121
|
-
# end
|
|
122
|
-
def message_count
|
|
123
|
-
@chat.message_count
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# --- Message Manipulation ---
|
|
127
|
-
|
|
128
|
-
# Replace all messages with new array
|
|
129
|
-
#
|
|
130
|
-
# @param new_messages [Array<RubyLLM::Message>] New message array
|
|
131
|
-
# @return [void]
|
|
132
|
-
#
|
|
133
|
-
# @example
|
|
134
|
-
# new_msgs = ctx.messages.reject { |m| m.role == :tool }
|
|
135
|
-
# ctx.replace_messages(new_msgs)
|
|
136
|
-
def replace_messages(new_messages)
|
|
137
|
-
@chat.replace_messages(new_messages)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Compress tool result messages to save context space
|
|
141
|
-
#
|
|
142
|
-
# Creates NEW message objects with truncated content (follows RubyLLM patterns).
|
|
143
|
-
# Truncates old tool results while keeping recent ones intact.
|
|
144
|
-
# Automatically marks compression as applied to prevent double compression.
|
|
145
|
-
#
|
|
146
|
-
# @param keep_recent [Integer] Number of recent tool results to preserve (default: 10)
|
|
147
|
-
# @param truncate_to [Integer] Max characters for truncated results (default: 200)
|
|
148
|
-
# @return [Integer] Number of messages compressed
|
|
149
|
-
#
|
|
150
|
-
# @example Light compression at 60%
|
|
151
|
-
# ctx.compress_tool_results(keep_recent: 15, truncate_to: 500)
|
|
152
|
-
#
|
|
153
|
-
# @example Aggressive compression at 80%
|
|
154
|
-
# ctx.compress_tool_results(keep_recent: 5, truncate_to: 100)
|
|
155
|
-
def compress_tool_results(keep_recent: 10, truncate_to: 200)
|
|
156
|
-
msgs = messages.dup
|
|
157
|
-
compressed_count = 0
|
|
158
|
-
|
|
159
|
-
# Find tool result messages (skip recent ones)
|
|
160
|
-
tool_indices = []
|
|
161
|
-
msgs.each_with_index do |msg, idx|
|
|
162
|
-
tool_indices << idx if msg.role == :tool
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Keep recent tool results, compress older ones
|
|
166
|
-
indices_to_compress = tool_indices[0...-keep_recent] || []
|
|
167
|
-
|
|
168
|
-
indices_to_compress.each do |idx|
|
|
169
|
-
msg = msgs[idx]
|
|
170
|
-
content = msg.content.to_s
|
|
171
|
-
next if content.length <= truncate_to
|
|
172
|
-
|
|
173
|
-
# Create NEW message with truncated content (NO instance_variable_set!)
|
|
174
|
-
truncated_content = "#{content[0...truncate_to]}... [truncated for context management]"
|
|
175
|
-
|
|
176
|
-
# Create new message object following RubyLLM patterns
|
|
177
|
-
msgs[idx] = RubyLLM::Message.new(
|
|
178
|
-
role: :tool,
|
|
179
|
-
content: truncated_content,
|
|
180
|
-
tool_call_id: msg.tool_call_id,
|
|
181
|
-
)
|
|
182
|
-
compressed_count += 1
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
replace_messages(msgs)
|
|
186
|
-
|
|
187
|
-
# Mark compression as applied to coordinate with ContextManager
|
|
188
|
-
mark_compression_applied
|
|
189
|
-
|
|
190
|
-
compressed_count
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
# Mark compression as applied in ContextManager
|
|
194
|
-
#
|
|
195
|
-
# Call this when your handler performs compression to prevent
|
|
196
|
-
# double compression from auto-compression logic.
|
|
197
|
-
#
|
|
198
|
-
# @return [void]
|
|
199
|
-
#
|
|
200
|
-
# @example Custom compression
|
|
201
|
-
# msgs = ctx.messages.map { |m| ... } # custom logic
|
|
202
|
-
# ctx.replace_messages(msgs)
|
|
203
|
-
# ctx.mark_compression_applied
|
|
204
|
-
def mark_compression_applied
|
|
205
|
-
return unless @chat.respond_to?(:context_manager)
|
|
206
|
-
|
|
207
|
-
@chat.context_manager.compression_applied = true
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
# Check if compression has already been applied
|
|
211
|
-
#
|
|
212
|
-
# @return [Boolean] True if compression was already applied
|
|
213
|
-
#
|
|
214
|
-
# @example Conditional compression
|
|
215
|
-
# unless ctx.compression_applied?
|
|
216
|
-
# ctx.compress_tool_results(keep_recent: 10)
|
|
217
|
-
# end
|
|
218
|
-
def compression_applied?
|
|
219
|
-
return false unless @chat.respond_to?(:context_manager)
|
|
220
|
-
|
|
221
|
-
!!@chat.context_manager.compression_applied
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
# Remove old messages from history
|
|
225
|
-
#
|
|
226
|
-
# Keeps system message (if any) and recent exchanges.
|
|
227
|
-
# This is more aggressive than compression and loses context.
|
|
228
|
-
#
|
|
229
|
-
# @param keep_recent [Integer] Number of recent messages to keep (default: 20)
|
|
230
|
-
# @return [Integer] Number of messages removed
|
|
231
|
-
#
|
|
232
|
-
# @example Prune at 80% threshold
|
|
233
|
-
# ctx.prune_old_messages(keep_recent: 30)
|
|
234
|
-
#
|
|
235
|
-
# @example Emergency pruning at 90%
|
|
236
|
-
# ctx.prune_old_messages(keep_recent: 10)
|
|
237
|
-
def prune_old_messages(keep_recent: 20)
|
|
238
|
-
msgs = messages.dup
|
|
239
|
-
original_count = msgs.size
|
|
240
|
-
|
|
241
|
-
# Always keep system message if present
|
|
242
|
-
system_msg = msgs.first if msgs.first&.role == :system
|
|
243
|
-
non_system = system_msg ? msgs[1..] : msgs
|
|
244
|
-
|
|
245
|
-
# Keep only recent messages
|
|
246
|
-
if non_system.size > keep_recent
|
|
247
|
-
kept = non_system.last(keep_recent)
|
|
248
|
-
new_msgs = system_msg ? [system_msg] + kept : kept
|
|
249
|
-
replace_messages(new_msgs)
|
|
250
|
-
original_count - new_msgs.size
|
|
251
|
-
else
|
|
252
|
-
0
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# Summarize old message exchanges
|
|
257
|
-
#
|
|
258
|
-
# Groups old user/assistant pairs and replaces with summary.
|
|
259
|
-
# This is a placeholder - actual implementation would use LLM.
|
|
260
|
-
#
|
|
261
|
-
# @param older_than [Integer] Messages older than this index get summarized
|
|
262
|
-
# @return [Integer] Number of exchanges summarized
|
|
263
|
-
#
|
|
264
|
-
# @example
|
|
265
|
-
# ctx.summarize_old_exchanges(older_than: 10)
|
|
266
|
-
def summarize_old_exchanges(older_than: 10)
|
|
267
|
-
# For now, this is a marker - full implementation would call LLM
|
|
268
|
-
# to summarize exchanges. We provide the API for developers to
|
|
269
|
-
# implement their own summarization logic.
|
|
270
|
-
0
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
# Custom message transformation
|
|
274
|
-
#
|
|
275
|
-
# Apply a block to transform messages. This gives full control
|
|
276
|
-
# over message manipulation for custom strategies.
|
|
277
|
-
#
|
|
278
|
-
# @yield [Array<RubyLLM::Message>] Current messages
|
|
279
|
-
# @yieldreturn [Array<RubyLLM::Message>] Transformed messages
|
|
280
|
-
# @return [void]
|
|
281
|
-
#
|
|
282
|
-
# @example Remove specific tool results
|
|
283
|
-
# ctx.transform_messages do |msgs|
|
|
284
|
-
# msgs.reject { |m| m.role == :tool && m.content.include?("verbose output") }
|
|
285
|
-
# end
|
|
286
|
-
#
|
|
287
|
-
# @example Custom compression logic
|
|
288
|
-
# ctx.transform_messages do |msgs|
|
|
289
|
-
# msgs.map do |m|
|
|
290
|
-
# if m.role == :tool && m.content.length > 1000
|
|
291
|
-
# RubyLLM::Message.new(role: :tool, content: m.content[0..500], tool_call_id: m.tool_call_id)
|
|
292
|
-
# else
|
|
293
|
-
# m
|
|
294
|
-
# end
|
|
295
|
-
# end
|
|
296
|
-
# end
|
|
297
|
-
def transform_messages
|
|
298
|
-
new_msgs = yield(messages.dup)
|
|
299
|
-
replace_messages(new_msgs)
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
# Log a context management action
|
|
303
|
-
#
|
|
304
|
-
# Emits a log event for tracking what actions were taken.
|
|
305
|
-
# Useful for debugging and monitoring context management strategies.
|
|
306
|
-
#
|
|
307
|
-
# @param action [String] Description of action taken
|
|
308
|
-
# @param details [Hash] Additional details
|
|
309
|
-
# @return [void]
|
|
310
|
-
#
|
|
311
|
-
# @example Log compression action
|
|
312
|
-
# ctx.log_action("compressed_tool_results", count: 5)
|
|
313
|
-
#
|
|
314
|
-
# @example Log emergency action
|
|
315
|
-
# ctx.log_action("emergency_pruning", remaining: ctx.tokens_remaining)
|
|
316
|
-
def log_action(action, details = {})
|
|
317
|
-
LogStream.emit(
|
|
318
|
-
type: "context_management_action",
|
|
319
|
-
agent: agent_name,
|
|
320
|
-
threshold: threshold,
|
|
321
|
-
action: action,
|
|
322
|
-
usage_percentage: usage_percentage,
|
|
323
|
-
**details,
|
|
324
|
-
)
|
|
325
|
-
end
|
|
326
|
-
end
|
|
327
|
-
end
|
|
328
|
-
end
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "delegate"
|
|
4
|
-
|
|
5
|
-
module SwarmSDK
|
|
6
|
-
# Registry for user-defined custom tools
|
|
7
|
-
#
|
|
8
|
-
# Provides a simple way to register custom tools without creating a full plugin.
|
|
9
|
-
# Custom tools are registered globally and available to all agents that request them.
|
|
10
|
-
#
|
|
11
|
-
# ## When to Use Custom Tools vs Plugins
|
|
12
|
-
#
|
|
13
|
-
# **Use Custom Tools when:**
|
|
14
|
-
# - You have simple, stateless tools
|
|
15
|
-
# - Tools don't need persistent storage
|
|
16
|
-
# - Tools don't need lifecycle hooks
|
|
17
|
-
# - Tools don't need system prompt contributions
|
|
18
|
-
#
|
|
19
|
-
# **Use Plugins when:**
|
|
20
|
-
# - Tools need persistent storage per agent
|
|
21
|
-
# - Tools need lifecycle hooks (on_agent_initialized, on_user_message, etc.)
|
|
22
|
-
# - Tools need to contribute to system prompts
|
|
23
|
-
# - You have a suite of related tools that share configuration
|
|
24
|
-
#
|
|
25
|
-
# @example Register a simple tool
|
|
26
|
-
# class WeatherTool < RubyLLM::Tool
|
|
27
|
-
# description "Get weather for a city"
|
|
28
|
-
# param :city, type: "string", required: true
|
|
29
|
-
#
|
|
30
|
-
# def execute(city:)
|
|
31
|
-
# "Weather in #{city}: Sunny"
|
|
32
|
-
# end
|
|
33
|
-
# end
|
|
34
|
-
#
|
|
35
|
-
# SwarmSDK.register_tool(WeatherTool)
|
|
36
|
-
#
|
|
37
|
-
# @example Register with explicit name
|
|
38
|
-
# SwarmSDK.register_tool(:Weather, WeatherTool)
|
|
39
|
-
#
|
|
40
|
-
# @example Tool with creation requirements
|
|
41
|
-
# class AgentAwareTool < RubyLLM::Tool
|
|
42
|
-
# def self.creation_requirements
|
|
43
|
-
# [:agent_name, :directory]
|
|
44
|
-
# end
|
|
45
|
-
#
|
|
46
|
-
# def initialize(agent_name:, directory:)
|
|
47
|
-
# super()
|
|
48
|
-
# @agent_name = agent_name
|
|
49
|
-
# @directory = directory
|
|
50
|
-
# end
|
|
51
|
-
#
|
|
52
|
-
# def execute
|
|
53
|
-
# "Agent: #{@agent_name}, Dir: #{@directory}"
|
|
54
|
-
# end
|
|
55
|
-
# end
|
|
56
|
-
#
|
|
57
|
-
# SwarmSDK.register_tool(AgentAwareTool)
|
|
58
|
-
#
|
|
59
|
-
module CustomToolRegistry
|
|
60
|
-
# Wrapper that overrides the tool's name to match the registered name
|
|
61
|
-
#
|
|
62
|
-
# This ensures that when a user registers a tool with a specific name,
|
|
63
|
-
# that name is what gets used for tool lookup (has_tool?) and LLM tool calls.
|
|
64
|
-
class NamedToolWrapper < SimpleDelegator
|
|
65
|
-
def initialize(tool, registered_name)
|
|
66
|
-
super(tool)
|
|
67
|
-
@registered_name = registered_name.to_s
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Override name to return the registered name
|
|
71
|
-
def name
|
|
72
|
-
@registered_name
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
@tools = {}
|
|
77
|
-
|
|
78
|
-
class << self
|
|
79
|
-
# Register a custom tool
|
|
80
|
-
#
|
|
81
|
-
# @param name [Symbol] Tool name
|
|
82
|
-
# @param tool_class [Class] Tool class (must be a RubyLLM::Tool subclass)
|
|
83
|
-
# @raise [ArgumentError] If tool_class is not a RubyLLM::Tool subclass
|
|
84
|
-
# @raise [ArgumentError] If a tool with the same name is already registered
|
|
85
|
-
# @return [void]
|
|
86
|
-
def register(name, tool_class)
|
|
87
|
-
name = name.to_sym
|
|
88
|
-
|
|
89
|
-
unless tool_class.is_a?(Class) && tool_class < RubyLLM::Tool
|
|
90
|
-
raise ArgumentError, "Tool class must inherit from RubyLLM::Tool"
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
if @tools.key?(name)
|
|
94
|
-
raise ArgumentError, "Custom tool '#{name}' is already registered"
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
if PluginRegistry.plugin_tool?(name)
|
|
98
|
-
raise ArgumentError, "Tool '#{name}' is already provided by a plugin"
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
if Tools::Registry.exists?(name)
|
|
102
|
-
raise ArgumentError, "Tool '#{name}' is a built-in tool and cannot be overridden"
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
@tools[name] = tool_class
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Check if a custom tool is registered
|
|
109
|
-
#
|
|
110
|
-
# @param name [Symbol, String] Tool name
|
|
111
|
-
# @return [Boolean]
|
|
112
|
-
def registered?(name)
|
|
113
|
-
@tools.key?(name.to_sym)
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Get a registered tool class
|
|
117
|
-
#
|
|
118
|
-
# @param name [Symbol, String] Tool name
|
|
119
|
-
# @return [Class, nil] Tool class or nil if not found
|
|
120
|
-
def get(name)
|
|
121
|
-
@tools[name.to_sym]
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Get all registered custom tool names
|
|
125
|
-
#
|
|
126
|
-
# @return [Array<Symbol>]
|
|
127
|
-
def tool_names
|
|
128
|
-
@tools.keys
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Create a tool instance
|
|
132
|
-
#
|
|
133
|
-
# Uses the tool's `creation_requirements` class method (if defined) to determine
|
|
134
|
-
# what parameters to pass to the constructor. The created tool is wrapped with
|
|
135
|
-
# NamedToolWrapper to ensure the registered name is used for tool lookup.
|
|
136
|
-
#
|
|
137
|
-
# @param name [Symbol, String] Tool name
|
|
138
|
-
# @param context [Hash] Available context for tool creation
|
|
139
|
-
# @option context [Symbol] :agent_name Agent identifier
|
|
140
|
-
# @option context [String] :directory Agent's working directory
|
|
141
|
-
# @return [RubyLLM::Tool] Instantiated tool (wrapped with registered name)
|
|
142
|
-
# @raise [ConfigurationError] If tool is unknown or has unmet requirements
|
|
143
|
-
def create(name, context = {})
|
|
144
|
-
name_sym = name.to_sym
|
|
145
|
-
tool_class = @tools[name_sym]
|
|
146
|
-
|
|
147
|
-
raise ConfigurationError, "Unknown custom tool: #{name}" unless tool_class
|
|
148
|
-
|
|
149
|
-
# Create the tool instance
|
|
150
|
-
tool = if tool_class.respond_to?(:creation_requirements)
|
|
151
|
-
requirements = tool_class.creation_requirements
|
|
152
|
-
params = extract_params(requirements, context, name)
|
|
153
|
-
tool_class.new(**params)
|
|
154
|
-
else
|
|
155
|
-
# No requirements - simple instantiation
|
|
156
|
-
tool_class.new
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Wrap with NamedToolWrapper to ensure registered name is used
|
|
160
|
-
NamedToolWrapper.new(tool, name_sym)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Unregister a custom tool
|
|
164
|
-
#
|
|
165
|
-
# @param name [Symbol, String] Tool name
|
|
166
|
-
# @return [Class, nil] The unregistered tool class, or nil if not found
|
|
167
|
-
def unregister(name)
|
|
168
|
-
@tools.delete(name.to_sym)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# Clear all registered custom tools
|
|
172
|
-
#
|
|
173
|
-
# Primarily useful for testing.
|
|
174
|
-
#
|
|
175
|
-
# @return [void]
|
|
176
|
-
def clear
|
|
177
|
-
@tools.clear
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Infer tool name from class name
|
|
181
|
-
#
|
|
182
|
-
# @param tool_class [Class] Tool class
|
|
183
|
-
# @return [Symbol] Inferred tool name
|
|
184
|
-
#
|
|
185
|
-
# @example
|
|
186
|
-
# infer_name(WeatherTool) #=> :Weather
|
|
187
|
-
# infer_name(MyApp::Tools::StockPrice) #=> :StockPrice
|
|
188
|
-
# infer_name(MyApp::Tools::StockPriceTool) #=> :StockPrice
|
|
189
|
-
def infer_name(tool_class)
|
|
190
|
-
# Get the class name without module prefix
|
|
191
|
-
class_name = tool_class.name.split("::").last
|
|
192
|
-
|
|
193
|
-
# Remove "Tool" suffix if present
|
|
194
|
-
name = class_name.sub(/Tool\z/, "")
|
|
195
|
-
|
|
196
|
-
name.to_sym
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
private
|
|
200
|
-
|
|
201
|
-
# Extract required parameters from context
|
|
202
|
-
#
|
|
203
|
-
# @param requirements [Array<Symbol>] Required parameter names
|
|
204
|
-
# @param context [Hash] Available context
|
|
205
|
-
# @param tool_name [Symbol] Tool name for error messages
|
|
206
|
-
# @return [Hash] Parameters to pass to tool constructor
|
|
207
|
-
# @raise [ConfigurationError] If required parameter is missing
|
|
208
|
-
def extract_params(requirements, context, tool_name)
|
|
209
|
-
params = {}
|
|
210
|
-
|
|
211
|
-
requirements.each do |req|
|
|
212
|
-
unless context.key?(req)
|
|
213
|
-
raise ConfigurationError,
|
|
214
|
-
"Custom tool '#{tool_name}' requires '#{req}' but it was not provided. " \
|
|
215
|
-
"Ensure the tool's `creation_requirements` only includes supported keys: " \
|
|
216
|
-
":agent_name, :directory"
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
params[req] = context[req]
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
params
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
end
|