swarm_memory 2.1.5 → 2.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/swarm_memory/version.rb +1 -1
- metadata +5 -184
- data/lib/claude_swarm/base_executor.rb +0 -133
- data/lib/claude_swarm/claude_code_executor.rb +0 -349
- data/lib/claude_swarm/claude_mcp_server.rb +0 -78
- data/lib/claude_swarm/cli.rb +0 -697
- data/lib/claude_swarm/commands/ps.rb +0 -215
- data/lib/claude_swarm/commands/show.rb +0 -139
- data/lib/claude_swarm/configuration.rb +0 -373
- data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
- data/lib/claude_swarm/json_handler.rb +0 -91
- data/lib/claude_swarm/mcp_generator.rb +0 -230
- data/lib/claude_swarm/openai/chat_completion.rb +0 -256
- data/lib/claude_swarm/openai/executor.rb +0 -256
- data/lib/claude_swarm/openai/responses.rb +0 -319
- data/lib/claude_swarm/orchestrator.rb +0 -878
- data/lib/claude_swarm/process_tracker.rb +0 -78
- data/lib/claude_swarm/session_cost_calculator.rb +0 -209
- data/lib/claude_swarm/session_path.rb +0 -42
- data/lib/claude_swarm/settings_generator.rb +0 -77
- data/lib/claude_swarm/system_utils.rb +0 -46
- data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
- data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
- data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
- data/lib/claude_swarm/tools/task_tool.rb +0 -63
- data/lib/claude_swarm/version.rb +0 -5
- data/lib/claude_swarm/worktree_manager.rb +0 -475
- data/lib/claude_swarm/yaml_loader.rb +0 -22
- data/lib/claude_swarm.rb +0 -67
- data/lib/swarm_cli/cli.rb +0 -201
- data/lib/swarm_cli/command_registry.rb +0 -61
- data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
- data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
- data/lib/swarm_cli/commands/migrate.rb +0 -55
- data/lib/swarm_cli/commands/run.rb +0 -173
- data/lib/swarm_cli/config_loader.rb +0 -98
- data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
- data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
- data/lib/swarm_cli/interactive_repl.rb +0 -924
- data/lib/swarm_cli/mcp_serve_options.rb +0 -44
- data/lib/swarm_cli/mcp_tools_options.rb +0 -59
- data/lib/swarm_cli/migrate_options.rb +0 -54
- data/lib/swarm_cli/migrator.rb +0 -132
- data/lib/swarm_cli/options.rb +0 -151
- data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
- data/lib/swarm_cli/ui/components/content_block.rb +0 -120
- data/lib/swarm_cli/ui/components/divider.rb +0 -57
- data/lib/swarm_cli/ui/components/panel.rb +0 -62
- data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
- data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
- data/lib/swarm_cli/ui/formatters/number.rb +0 -58
- data/lib/swarm_cli/ui/formatters/text.rb +0 -77
- data/lib/swarm_cli/ui/formatters/time.rb +0 -73
- data/lib/swarm_cli/ui/icons.rb +0 -36
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
- data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
- data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
- data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
- data/lib/swarm_cli/version.rb +0 -5
- data/lib/swarm_cli.rb +0 -46
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
- data/lib/swarm_sdk/agent/builder.rb +0 -552
- data/lib/swarm_sdk/agent/chat.rb +0 -774
- data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
- 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 -78
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
- 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 -136
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
- data/lib/swarm_sdk/agent/context.rb +0 -116
- data/lib/swarm_sdk/agent/context_manager.rb +0 -315
- data/lib/swarm_sdk/agent/definition.rb +0 -477
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
- data/lib/swarm_sdk/builders/base_builder.rb +0 -409
- 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/configuration/parser.rb +0 -353
- data/lib/swarm_sdk/configuration/translator.rb +0 -255
- data/lib/swarm_sdk/configuration.rb +0 -135
- data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
- data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
- 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/defaults.rb +0 -196
- 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 -255
- 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 -1
- data/lib/swarm_sdk/models.rb +0 -120
- 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 -123
- 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 -683
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
- data/lib/swarm_sdk/swarm/builder.rb +0 -249
- data/lib/swarm_sdk/swarm/executor.rb +0 -213
- data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
- data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
- data/lib/swarm_sdk/swarm.rb +0 -717
- data/lib/swarm_sdk/swarm_loader.rb +0 -145
- data/lib/swarm_sdk/swarm_registry.rb +0 -136
- data/lib/swarm_sdk/tools/bash.rb +0 -282
- data/lib/swarm_sdk/tools/clock.rb +0 -44
- data/lib/swarm_sdk/tools/delegate.rb +0 -267
- 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 -163
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
- 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 -272
- 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 -98
- data/lib/swarm_sdk/tools/todo_write.rb +0 -235
- data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
- data/lib/swarm_sdk/tools/write.rb +0 -112
- 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 -79
- data/lib/swarm_sdk/workflow/builder.rb +0 -143
- data/lib/swarm_sdk/workflow/executor.rb +0 -497
- data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
- data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
- data/lib/swarm_sdk/workflow.rb +0 -554
- data/lib/swarm_sdk.rb +0 -524
data/lib/swarm_sdk/agent/chat.rb
DELETED
|
@@ -1,774 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
module Agent
|
|
5
|
-
# Chat wraps RubyLLM::Chat to provide SwarmSDK orchestration capabilities
|
|
6
|
-
#
|
|
7
|
-
# ## Architecture
|
|
8
|
-
#
|
|
9
|
-
# This class uses **composition** with RubyLLM::Chat:
|
|
10
|
-
# - RubyLLM::Chat handles: LLM API, messages, tools, concurrent execution
|
|
11
|
-
# - SwarmSDK::Agent::Chat adds: hooks, reminders, semaphores, event enrichment
|
|
12
|
-
#
|
|
13
|
-
# ## ChatHelpers Module Architecture
|
|
14
|
-
#
|
|
15
|
-
# Chat is decomposed into 8 focused helper modules to manage complexity:
|
|
16
|
-
#
|
|
17
|
-
# ### Core Functionality
|
|
18
|
-
# - **EventEmitter**: Multi-subscriber event callbacks for tool/lifecycle events.
|
|
19
|
-
# Provides `subscribe`, `emit_event`, `clear_subscribers` for observable behavior.
|
|
20
|
-
# - **LoggingHelpers**: Formatting tool call information for structured JSON logs.
|
|
21
|
-
# Converts tool calls/results to loggable hashes with sanitization.
|
|
22
|
-
# - **LlmConfiguration**: Model selection, provider setup, and API configuration.
|
|
23
|
-
# Resolves provider from model, handles model aliases, builds connection config.
|
|
24
|
-
# - **SystemReminders**: Dynamic system message injection based on agent state.
|
|
25
|
-
# Collects reminders from plugins, context trackers, and other sources.
|
|
26
|
-
#
|
|
27
|
-
# ### Cross-Cutting Concerns
|
|
28
|
-
# - **Instrumentation**: LLM API request/response logging via Faraday middleware.
|
|
29
|
-
# Wraps HTTP calls to capture timing, tokens, and error information.
|
|
30
|
-
# - **HookIntegration**: Pre/post tool execution callbacks and delegation hooks.
|
|
31
|
-
# Integrates with SwarmSDK Hooks::Registry for lifecycle events.
|
|
32
|
-
# - **TokenTracking**: Usage statistics and cost calculation per conversation.
|
|
33
|
-
# Accumulates input/output tokens across all LLM calls.
|
|
34
|
-
#
|
|
35
|
-
# ### State Management
|
|
36
|
-
# - **Serialization**: Snapshot/restore for session persistence.
|
|
37
|
-
# Saves/restores message history, tool states, and agent context.
|
|
38
|
-
#
|
|
39
|
-
# ## Module Dependencies
|
|
40
|
-
#
|
|
41
|
-
# EventEmitter <-- HookIntegration (event emission for hooks)
|
|
42
|
-
# TokenTracking <-- Instrumentation (usage data collection)
|
|
43
|
-
# SystemReminders <-- uses ContextTracker instance (not a module)
|
|
44
|
-
# LoggingHelpers <-- EventEmitter (log event formatting)
|
|
45
|
-
#
|
|
46
|
-
# ## Design Rationale
|
|
47
|
-
#
|
|
48
|
-
# This decomposition follows Single Responsibility Principle. Each module
|
|
49
|
-
# handles one concern. They access shared Chat internals (@llm_chat,
|
|
50
|
-
# @messages, etc.) which makes them tightly coupled to Chat, but this keeps
|
|
51
|
-
# the main Chat class focused on orchestration rather than implementation
|
|
52
|
-
# details. The modules are intentionally NOT standalone - they augment
|
|
53
|
-
# Chat with specific capabilities.
|
|
54
|
-
#
|
|
55
|
-
# ## Rate Limiting Strategy
|
|
56
|
-
#
|
|
57
|
-
# Two-level semaphore system prevents API quota exhaustion in hierarchical agent trees:
|
|
58
|
-
# 1. **Global semaphore** - Serializes ask() calls across entire swarm
|
|
59
|
-
# 2. **Local semaphore** - Limits concurrent tool calls per agent (via RubyLLM)
|
|
60
|
-
#
|
|
61
|
-
# ## Event Flow
|
|
62
|
-
#
|
|
63
|
-
# RubyLLM events → SwarmSDK subscribes → enriches with context → emits SwarmSDK events
|
|
64
|
-
# This allows hooks to fire on SwarmSDK events with full agent context.
|
|
65
|
-
#
|
|
66
|
-
# @see ChatHelpers::EventEmitter Event subscription and emission
|
|
67
|
-
# @see ChatHelpers::Instrumentation API logging via Faraday middleware
|
|
68
|
-
# @see ChatHelpers::Serialization State persistence (snapshot/restore)
|
|
69
|
-
# @see ChatHelpers::HookIntegration Pre/post tool execution callbacks
|
|
70
|
-
class Chat
|
|
71
|
-
# Include event emitter for multi-subscriber callbacks
|
|
72
|
-
include ChatHelpers::EventEmitter
|
|
73
|
-
|
|
74
|
-
# Include logging helpers for tool call formatting
|
|
75
|
-
include ChatHelpers::LoggingHelpers
|
|
76
|
-
|
|
77
|
-
# Include hook integration for pre/post tool hooks
|
|
78
|
-
include ChatHelpers::HookIntegration
|
|
79
|
-
|
|
80
|
-
# Include LLM configuration helpers
|
|
81
|
-
include ChatHelpers::LlmConfiguration
|
|
82
|
-
|
|
83
|
-
# Include system reminder collection
|
|
84
|
-
include ChatHelpers::SystemReminders
|
|
85
|
-
|
|
86
|
-
# Include token tracking methods
|
|
87
|
-
include ChatHelpers::TokenTracking
|
|
88
|
-
|
|
89
|
-
# Include message serialization
|
|
90
|
-
include ChatHelpers::Serialization
|
|
91
|
-
|
|
92
|
-
# Include LLM instrumentation
|
|
93
|
-
include ChatHelpers::Instrumentation
|
|
94
|
-
|
|
95
|
-
# SwarmSDK-specific accessors
|
|
96
|
-
attr_reader :global_semaphore,
|
|
97
|
-
:real_model_info,
|
|
98
|
-
:context_tracker,
|
|
99
|
-
:context_manager,
|
|
100
|
-
:agent_context,
|
|
101
|
-
:last_todowrite_message_index,
|
|
102
|
-
:active_skill_path,
|
|
103
|
-
:provider # Extracted from RubyLLM::Chat for instrumentation (not publicly accessible)
|
|
104
|
-
|
|
105
|
-
# Setters for snapshot/restore
|
|
106
|
-
attr_writer :last_todowrite_message_index, :active_skill_path
|
|
107
|
-
|
|
108
|
-
# Initialize AgentChat with RubyLLM::Chat wrapper
|
|
109
|
-
#
|
|
110
|
-
# @param definition [Hash] Agent definition containing all configuration
|
|
111
|
-
# @param agent_name [Symbol, nil] Agent identifier (for plugin callbacks)
|
|
112
|
-
# @param global_semaphore [Async::Semaphore, nil] Shared across all agents
|
|
113
|
-
# @param options [Hash] Additional options
|
|
114
|
-
def initialize(definition:, agent_name: nil, global_semaphore: nil, **options)
|
|
115
|
-
# Initialize event emitter system
|
|
116
|
-
initialize_event_emitter
|
|
117
|
-
|
|
118
|
-
# Extract configuration from definition
|
|
119
|
-
model_id = definition[:model]
|
|
120
|
-
provider_name = definition[:provider]
|
|
121
|
-
context_window = definition[:context_window]
|
|
122
|
-
max_concurrent_tools = definition[:max_concurrent_tools]
|
|
123
|
-
base_url = definition[:base_url]
|
|
124
|
-
api_version = definition[:api_version]
|
|
125
|
-
timeout = definition[:timeout] || Defaults::Timeouts::AGENT_REQUEST_SECONDS
|
|
126
|
-
assume_model_exists = definition[:assume_model_exists]
|
|
127
|
-
system_prompt = definition[:system_prompt]
|
|
128
|
-
parameters = definition[:parameters]
|
|
129
|
-
custom_headers = definition[:headers]
|
|
130
|
-
|
|
131
|
-
# Agent identifier (for plugin callbacks)
|
|
132
|
-
@agent_name = agent_name
|
|
133
|
-
|
|
134
|
-
# Context manager for ephemeral messages
|
|
135
|
-
@context_manager = ContextManager.new
|
|
136
|
-
|
|
137
|
-
# Rate limiting
|
|
138
|
-
@global_semaphore = global_semaphore
|
|
139
|
-
@explicit_context_window = context_window
|
|
140
|
-
|
|
141
|
-
# Serialize ask() calls to prevent message corruption
|
|
142
|
-
@ask_semaphore = Async::Semaphore.new(1)
|
|
143
|
-
|
|
144
|
-
# Track TodoWrite usage for periodic reminders
|
|
145
|
-
@last_todowrite_message_index = nil
|
|
146
|
-
|
|
147
|
-
# Agent context for logging (set via setup_context)
|
|
148
|
-
@agent_context = nil
|
|
149
|
-
|
|
150
|
-
# Context tracker (created after agent_context is set)
|
|
151
|
-
@context_tracker = nil
|
|
152
|
-
|
|
153
|
-
# Track immutable tools
|
|
154
|
-
@immutable_tool_names = Set.new(["Think", "Clock", "TodoWrite"])
|
|
155
|
-
|
|
156
|
-
# Track active skill (only used if memory enabled)
|
|
157
|
-
@active_skill_path = nil
|
|
158
|
-
|
|
159
|
-
# Create internal RubyLLM::Chat instance
|
|
160
|
-
@llm_chat = create_llm_chat(
|
|
161
|
-
model_id: model_id,
|
|
162
|
-
provider_name: provider_name,
|
|
163
|
-
base_url: base_url,
|
|
164
|
-
api_version: api_version,
|
|
165
|
-
timeout: timeout,
|
|
166
|
-
assume_model_exists: assume_model_exists,
|
|
167
|
-
max_concurrent_tools: max_concurrent_tools,
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
# Extract provider from RubyLLM::Chat for instrumentation
|
|
171
|
-
# Must be done after create_llm_chat since with_responses_api() may swap provider
|
|
172
|
-
# NOTE: RubyLLM doesn't expose provider publicly, but we need it for Faraday middleware
|
|
173
|
-
# rubocop:disable Security/NoReflectionMethods
|
|
174
|
-
@provider = @llm_chat.instance_variable_get(:@provider)
|
|
175
|
-
# rubocop:enable Security/NoReflectionMethods
|
|
176
|
-
|
|
177
|
-
# Try to fetch real model info for accurate context tracking
|
|
178
|
-
fetch_real_model_info(model_id)
|
|
179
|
-
|
|
180
|
-
# Configure system prompt, parameters, and headers
|
|
181
|
-
configure_system_prompt(system_prompt) if system_prompt
|
|
182
|
-
configure_parameters(parameters)
|
|
183
|
-
configure_headers(custom_headers)
|
|
184
|
-
|
|
185
|
-
# Setup around_tool_execution hook for SwarmSDK orchestration
|
|
186
|
-
setup_tool_execution_hook
|
|
187
|
-
|
|
188
|
-
# Setup around_llm_request hook for ephemeral message injection
|
|
189
|
-
setup_llm_request_hook
|
|
190
|
-
|
|
191
|
-
# Setup event bridging from RubyLLM to SwarmSDK
|
|
192
|
-
setup_event_bridging
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# --- SwarmSDK Abstraction API ---
|
|
196
|
-
# These methods provide SwarmSDK-specific semantics without exposing RubyLLM internals
|
|
197
|
-
|
|
198
|
-
# Model information
|
|
199
|
-
def model_id
|
|
200
|
-
@llm_chat.model.id
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def model_provider
|
|
204
|
-
@llm_chat.model.provider
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def model_context_window
|
|
208
|
-
@real_model_info&.context_window || @llm_chat.model.context_window
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Tool introspection
|
|
212
|
-
def has_tool?(name)
|
|
213
|
-
@llm_chat.tools.key?(name.to_s) || @llm_chat.tools.key?(name.to_sym)
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def tool_names
|
|
217
|
-
@llm_chat.tools.values.map(&:name).sort
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def tool_count
|
|
221
|
-
@llm_chat.tools.size
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def remove_tool(name)
|
|
225
|
-
@llm_chat.tools.delete(name.to_s) || @llm_chat.tools.delete(name.to_sym)
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
# Direct access to tools hash for advanced operations
|
|
229
|
-
#
|
|
230
|
-
# Use with caution - prefer has_tool?, tool_names, remove_tool for most cases.
|
|
231
|
-
# This is provided for:
|
|
232
|
-
# - Direct tool execution in tests
|
|
233
|
-
# - Advanced tool manipulation (remove_mutable_tools)
|
|
234
|
-
#
|
|
235
|
-
# @return [Hash] Tool name to tool instance mapping
|
|
236
|
-
def tools
|
|
237
|
-
@llm_chat.tools
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# Message introspection
|
|
241
|
-
def message_count
|
|
242
|
-
@llm_chat.messages.size
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def has_user_message?
|
|
246
|
-
@llm_chat.messages.any? { |msg| msg.role == :user }
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def last_assistant_message
|
|
250
|
-
@llm_chat.messages.reverse.find { |msg| msg.role == :assistant }
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# Read-only access to conversation messages
|
|
254
|
-
#
|
|
255
|
-
# Returns a copy of the message array for safe enumeration.
|
|
256
|
-
# External code should use this instead of internal_messages.
|
|
257
|
-
#
|
|
258
|
-
# @return [Array<RubyLLM::Message>] Copy of message array
|
|
259
|
-
def messages
|
|
260
|
-
@llm_chat.messages.dup
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
# Atomically replace all conversation messages
|
|
264
|
-
#
|
|
265
|
-
# Used for context compaction and state restoration.
|
|
266
|
-
# This is the safe way to manipulate messages from external code.
|
|
267
|
-
#
|
|
268
|
-
# @param new_messages [Array<RubyLLM::Message>] New message array
|
|
269
|
-
# @return [self] for chaining
|
|
270
|
-
def replace_messages(new_messages)
|
|
271
|
-
@llm_chat.messages.clear
|
|
272
|
-
new_messages.each { |msg| @llm_chat.messages << msg }
|
|
273
|
-
self
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Get all assistant messages
|
|
277
|
-
#
|
|
278
|
-
# @return [Array<RubyLLM::Message>] All assistant messages
|
|
279
|
-
def assistant_messages
|
|
280
|
-
@llm_chat.messages.select { |msg| msg.role == :assistant }
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# Find the last message matching a condition
|
|
284
|
-
#
|
|
285
|
-
# @yield [msg] Block to test each message
|
|
286
|
-
# @return [RubyLLM::Message, nil] Last matching message or nil
|
|
287
|
-
def find_last_message(&block)
|
|
288
|
-
@llm_chat.messages.reverse.find(&block)
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
# Find the index of last message matching a condition
|
|
292
|
-
#
|
|
293
|
-
# @yield [msg] Block to test each message
|
|
294
|
-
# @return [Integer, nil] Index of last matching message or nil
|
|
295
|
-
def find_last_message_index(&block)
|
|
296
|
-
@llm_chat.messages.rindex(&block)
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
# Get tool names that are NOT delegation tools
|
|
300
|
-
#
|
|
301
|
-
# @return [Array<String>] Non-delegation tool names
|
|
302
|
-
def non_delegation_tool_names
|
|
303
|
-
if @agent_context
|
|
304
|
-
@llm_chat.tools.keys.reject { |name| @agent_context.delegation_tool?(name.to_s) }
|
|
305
|
-
else
|
|
306
|
-
@llm_chat.tools.keys
|
|
307
|
-
end
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
# Add an ephemeral reminder to the most recent message
|
|
311
|
-
#
|
|
312
|
-
# The reminder will be sent to the LLM but not persisted in message history.
|
|
313
|
-
# This encapsulates the internal message array access.
|
|
314
|
-
#
|
|
315
|
-
# @param reminder [String] Reminder content to add
|
|
316
|
-
# @return [void]
|
|
317
|
-
def add_ephemeral_reminder(reminder)
|
|
318
|
-
@context_manager&.add_ephemeral_reminder(reminder, messages_array: @llm_chat.messages)
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
# --- Setup Methods ---
|
|
322
|
-
|
|
323
|
-
# Setup agent context
|
|
324
|
-
#
|
|
325
|
-
# @param context [Agent::Context] Agent context for this chat
|
|
326
|
-
def setup_context(context)
|
|
327
|
-
@agent_context = context
|
|
328
|
-
@context_tracker = ChatHelpers::ContextTracker.new(self, context)
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
# Setup logging callbacks
|
|
332
|
-
#
|
|
333
|
-
# @return [void]
|
|
334
|
-
def setup_logging
|
|
335
|
-
raise StateError, "Agent context not set. Call setup_context first." unless @agent_context
|
|
336
|
-
|
|
337
|
-
@context_tracker.setup_logging
|
|
338
|
-
inject_llm_instrumentation
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
# Emit model lookup warning if one occurred during initialization
|
|
342
|
-
#
|
|
343
|
-
# @param agent_name [Symbol, String] The agent name for logging context
|
|
344
|
-
def emit_model_lookup_warning(agent_name)
|
|
345
|
-
return unless @model_lookup_error
|
|
346
|
-
|
|
347
|
-
LogStream.emit(
|
|
348
|
-
type: "model_lookup_warning",
|
|
349
|
-
agent: agent_name,
|
|
350
|
-
swarm_id: @agent_context&.swarm_id,
|
|
351
|
-
parent_swarm_id: @agent_context&.parent_swarm_id,
|
|
352
|
-
model: @model_lookup_error[:model],
|
|
353
|
-
error_message: @model_lookup_error[:error_message],
|
|
354
|
-
suggestions: @model_lookup_error[:suggestions].map { |s| { id: s.id, name: s.name, context_window: s.context_window } },
|
|
355
|
-
)
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
# --- Adapter API (SwarmSDK-stable interface) ---
|
|
359
|
-
|
|
360
|
-
# Configure system prompt for the conversation
|
|
361
|
-
#
|
|
362
|
-
# @param prompt [String] System prompt
|
|
363
|
-
# @param replace [Boolean] Replace existing system messages if true
|
|
364
|
-
# @return [self] for chaining
|
|
365
|
-
def configure_system_prompt(prompt, replace: false)
|
|
366
|
-
@llm_chat.with_instructions(prompt, replace: replace)
|
|
367
|
-
self
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
# Add a tool to this chat
|
|
371
|
-
#
|
|
372
|
-
# @param tool [Class, RubyLLM::Tool] Tool class or instance
|
|
373
|
-
# @return [self] for chaining
|
|
374
|
-
def add_tool(tool)
|
|
375
|
-
@llm_chat.with_tool(tool)
|
|
376
|
-
self
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
# Complete the current conversation (no additional prompt)
|
|
380
|
-
#
|
|
381
|
-
# Delegates to RubyLLM::Chat#complete() which handles:
|
|
382
|
-
# - LLM API calls (with around_llm_request hook for ephemeral injection)
|
|
383
|
-
# - Tool execution (with around_tool_execution hook for SwarmSDK hooks)
|
|
384
|
-
# - Automatic tool loop (continues until no more tool calls)
|
|
385
|
-
#
|
|
386
|
-
# SwarmSDK adds:
|
|
387
|
-
# - Semaphore rate limiting (ask + global)
|
|
388
|
-
# - Finish marker handling (finish_agent, finish_swarm)
|
|
389
|
-
#
|
|
390
|
-
# @param options [Hash] Additional options (currently unused, for future compatibility)
|
|
391
|
-
# @param block [Proc] Optional streaming block
|
|
392
|
-
# @return [RubyLLM::Message] LLM response
|
|
393
|
-
def complete(**_options, &block)
|
|
394
|
-
@ask_semaphore.acquire do
|
|
395
|
-
execute_with_global_semaphore do
|
|
396
|
-
result = catch(:finish_agent) do
|
|
397
|
-
catch(:finish_swarm) do
|
|
398
|
-
# Delegate to RubyLLM::Chat#complete()
|
|
399
|
-
# Hooks handle ephemeral injection and tool orchestration
|
|
400
|
-
@llm_chat.complete(&block)
|
|
401
|
-
end
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
# Handle finish markers thrown by hooks
|
|
405
|
-
handle_finish_marker(result)
|
|
406
|
-
end
|
|
407
|
-
end
|
|
408
|
-
end
|
|
409
|
-
|
|
410
|
-
# Mark tools as immutable (cannot be removed by dynamic tool swapping)
|
|
411
|
-
#
|
|
412
|
-
# @param tool_names [Array<String>] Tool names to mark as immutable
|
|
413
|
-
def mark_tools_immutable(*tool_names)
|
|
414
|
-
@immutable_tool_names.merge(tool_names.flatten.map(&:to_s))
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
# Remove all mutable tools (keeps immutable tools)
|
|
418
|
-
#
|
|
419
|
-
# @return [void]
|
|
420
|
-
def remove_mutable_tools
|
|
421
|
-
mutable_tool_names = tools.keys.reject { |name| @immutable_tool_names.include?(name.to_s) }
|
|
422
|
-
mutable_tool_names.each { |name| tools.delete(name) }
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
# Mark skill as loaded (tracking for debugging/logging)
|
|
426
|
-
#
|
|
427
|
-
# @param file_path [String] Path to loaded skill
|
|
428
|
-
def mark_skill_loaded(file_path)
|
|
429
|
-
@active_skill_path = file_path
|
|
430
|
-
end
|
|
431
|
-
|
|
432
|
-
# Check if a skill is currently loaded
|
|
433
|
-
#
|
|
434
|
-
# @return [Boolean] True if a skill has been loaded
|
|
435
|
-
def skill_loaded?
|
|
436
|
-
!@active_skill_path.nil?
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
# Clear conversation history
|
|
440
|
-
#
|
|
441
|
-
# @return [void]
|
|
442
|
-
def clear_conversation
|
|
443
|
-
@llm_chat.reset_messages!
|
|
444
|
-
@context_manager&.clear_ephemeral
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
# --- Core Conversation Methods ---
|
|
448
|
-
|
|
449
|
-
# Send a message to the LLM and get a response
|
|
450
|
-
#
|
|
451
|
-
# This method:
|
|
452
|
-
# 1. Serializes concurrent asks via @ask_semaphore
|
|
453
|
-
# 2. Adds CLEAN user message to history (no reminders)
|
|
454
|
-
# 3. Injects system reminders as ephemeral content (sent to LLM but not stored)
|
|
455
|
-
# 4. Triggers user_prompt hooks
|
|
456
|
-
# 5. Acquires global semaphore for LLM call
|
|
457
|
-
# 6. Delegates to RubyLLM::Chat for actual execution
|
|
458
|
-
#
|
|
459
|
-
# @param prompt [String] User prompt
|
|
460
|
-
# @param options [Hash] Additional options (source: for hooks)
|
|
461
|
-
# @return [RubyLLM::Message] LLM response
|
|
462
|
-
def ask(prompt, **options)
|
|
463
|
-
@ask_semaphore.acquire do
|
|
464
|
-
is_first = first_message?
|
|
465
|
-
|
|
466
|
-
# Collect system reminders to inject as ephemeral content
|
|
467
|
-
reminders = collect_system_reminders(prompt, is_first)
|
|
468
|
-
|
|
469
|
-
# Trigger user_prompt hook (with clean prompt, not reminders)
|
|
470
|
-
source = options.delete(:source) || "user"
|
|
471
|
-
final_prompt = prompt
|
|
472
|
-
if @hook_executor
|
|
473
|
-
hook_result = trigger_user_prompt(prompt, source: source)
|
|
474
|
-
|
|
475
|
-
if hook_result[:halted]
|
|
476
|
-
return RubyLLM::Message.new(
|
|
477
|
-
role: :assistant,
|
|
478
|
-
content: hook_result[:halt_message],
|
|
479
|
-
model_id: model_id,
|
|
480
|
-
)
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
final_prompt = hook_result[:modified_prompt] if hook_result[:modified_prompt]
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
# Add CLEAN user message to history (no reminders embedded)
|
|
487
|
-
@llm_chat.add_message(role: :user, content: final_prompt)
|
|
488
|
-
|
|
489
|
-
# Track reminders as ephemeral content for this LLM call only
|
|
490
|
-
# They'll be injected by around_llm_request hook but not stored
|
|
491
|
-
reminders.each do |reminder|
|
|
492
|
-
@context_manager.add_ephemeral_reminder(reminder, messages_array: @llm_chat.messages)
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
# Execute complete() which handles tool loop and ephemeral injection
|
|
496
|
-
response = execute_with_global_semaphore do
|
|
497
|
-
catch(:finish_agent) do
|
|
498
|
-
catch(:finish_swarm) do
|
|
499
|
-
@llm_chat.complete(**options)
|
|
500
|
-
end
|
|
501
|
-
end
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
# Handle finish markers from hooks
|
|
505
|
-
handle_finish_marker(response)
|
|
506
|
-
end
|
|
507
|
-
end
|
|
508
|
-
|
|
509
|
-
# Add a message to the conversation history
|
|
510
|
-
#
|
|
511
|
-
# Automatically extracts and strips system reminders, tracking them as ephemeral.
|
|
512
|
-
#
|
|
513
|
-
# @param message_or_attributes [RubyLLM::Message, Hash] Message object or attributes hash
|
|
514
|
-
# @return [RubyLLM::Message] The added message
|
|
515
|
-
def add_message(message_or_attributes)
|
|
516
|
-
message = if message_or_attributes.is_a?(RubyLLM::Message)
|
|
517
|
-
message_or_attributes
|
|
518
|
-
else
|
|
519
|
-
RubyLLM::Message.new(message_or_attributes)
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
# Extract system reminders if present
|
|
523
|
-
content_str = message.content.is_a?(RubyLLM::Content) ? message.content.text : message.content.to_s
|
|
524
|
-
|
|
525
|
-
if @context_manager.has_system_reminders?(content_str)
|
|
526
|
-
reminders = @context_manager.extract_system_reminders(content_str)
|
|
527
|
-
clean_content_str = @context_manager.strip_system_reminders(content_str)
|
|
528
|
-
|
|
529
|
-
clean_content = if message.content.is_a?(RubyLLM::Content)
|
|
530
|
-
RubyLLM::Content.new(clean_content_str, message.content.attachments)
|
|
531
|
-
else
|
|
532
|
-
clean_content_str
|
|
533
|
-
end
|
|
534
|
-
|
|
535
|
-
clean_message = RubyLLM::Message.new(
|
|
536
|
-
role: message.role,
|
|
537
|
-
content: clean_content,
|
|
538
|
-
tool_call_id: message.tool_call_id,
|
|
539
|
-
tool_calls: message.tool_calls,
|
|
540
|
-
model_id: message.model_id,
|
|
541
|
-
input_tokens: message.input_tokens,
|
|
542
|
-
output_tokens: message.output_tokens,
|
|
543
|
-
cached_tokens: message.cached_tokens,
|
|
544
|
-
cache_creation_tokens: message.cache_creation_tokens,
|
|
545
|
-
)
|
|
546
|
-
|
|
547
|
-
@llm_chat.add_message(clean_message)
|
|
548
|
-
|
|
549
|
-
# Track reminders as ephemeral
|
|
550
|
-
reminders.each do |reminder|
|
|
551
|
-
@context_manager.add_ephemeral_reminder(reminder, messages_array: messages)
|
|
552
|
-
end
|
|
553
|
-
|
|
554
|
-
clean_message
|
|
555
|
-
else
|
|
556
|
-
@llm_chat.add_message(message)
|
|
557
|
-
end
|
|
558
|
-
end
|
|
559
|
-
|
|
560
|
-
private
|
|
561
|
-
|
|
562
|
-
# --- Tool Execution Hook ---
|
|
563
|
-
|
|
564
|
-
# Setup around_tool_execution hook for SwarmSDK orchestration
|
|
565
|
-
#
|
|
566
|
-
# This hook intercepts all tool executions to:
|
|
567
|
-
# - Trigger pre_tool_use hooks (can block, replace, or finish)
|
|
568
|
-
# - Trigger post_tool_use hooks (can transform results)
|
|
569
|
-
# - Handle finish markers
|
|
570
|
-
def setup_tool_execution_hook
|
|
571
|
-
@llm_chat.around_tool_execution do |tool_call, _tool_instance, execute|
|
|
572
|
-
# Skip hooks for delegation tools (they have their own events)
|
|
573
|
-
if delegation_tool_call?(tool_call)
|
|
574
|
-
execute.call
|
|
575
|
-
else
|
|
576
|
-
# PRE-HOOK
|
|
577
|
-
pre_result = trigger_pre_tool_use(tool_call)
|
|
578
|
-
|
|
579
|
-
case pre_result
|
|
580
|
-
when Hash
|
|
581
|
-
if pre_result[:finish_agent]
|
|
582
|
-
throw(:finish_agent, { __finish_agent__: true, message: pre_result[:custom_result] })
|
|
583
|
-
elsif pre_result[:finish_swarm]
|
|
584
|
-
throw(:finish_swarm, { __finish_swarm__: true, message: pre_result[:custom_result] })
|
|
585
|
-
elsif !pre_result[:proceed]
|
|
586
|
-
# Blocked - return custom result without executing
|
|
587
|
-
next pre_result[:custom_result] || "Tool execution blocked by hook"
|
|
588
|
-
end
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
# EXECUTE tool (no retry - failures are returned to LLM)
|
|
592
|
-
result = execute.call
|
|
593
|
-
|
|
594
|
-
# POST-HOOK
|
|
595
|
-
post_result = trigger_post_tool_use(result, tool_call: tool_call)
|
|
596
|
-
|
|
597
|
-
# Check for finish markers from post-hook
|
|
598
|
-
if post_result.is_a?(Hash)
|
|
599
|
-
if post_result[:__finish_agent__]
|
|
600
|
-
throw(:finish_agent, post_result)
|
|
601
|
-
elsif post_result[:__finish_swarm__]
|
|
602
|
-
throw(:finish_swarm, post_result)
|
|
603
|
-
end
|
|
604
|
-
end
|
|
605
|
-
|
|
606
|
-
post_result
|
|
607
|
-
end
|
|
608
|
-
end
|
|
609
|
-
end
|
|
610
|
-
|
|
611
|
-
# --- Event Bridging ---
|
|
612
|
-
|
|
613
|
-
# Setup event bridging from RubyLLM to SwarmSDK
|
|
614
|
-
#
|
|
615
|
-
# Subscribes to RubyLLM events and emits enriched SwarmSDK events.
|
|
616
|
-
def setup_event_bridging
|
|
617
|
-
# Bridge tool_call events
|
|
618
|
-
@llm_chat.on_tool_call do |tool_call|
|
|
619
|
-
emit(:tool_call, tool_call)
|
|
620
|
-
end
|
|
621
|
-
|
|
622
|
-
# Bridge tool_result events
|
|
623
|
-
@llm_chat.on_tool_result do |_tool_call, result|
|
|
624
|
-
emit(:tool_result, result)
|
|
625
|
-
end
|
|
626
|
-
|
|
627
|
-
# Bridge new_message events
|
|
628
|
-
@llm_chat.on_new_message do
|
|
629
|
-
emit(:new_message)
|
|
630
|
-
end
|
|
631
|
-
|
|
632
|
-
# Bridge end_message events (used for agent_step/agent_stop)
|
|
633
|
-
@llm_chat.on_end_message do |message|
|
|
634
|
-
emit(:end_message, message)
|
|
635
|
-
end
|
|
636
|
-
end
|
|
637
|
-
|
|
638
|
-
# --- LLM Request Hook ---
|
|
639
|
-
|
|
640
|
-
# Setup around_llm_request hook for ephemeral message injection
|
|
641
|
-
#
|
|
642
|
-
# This hook intercepts all LLM API calls to:
|
|
643
|
-
# - Inject ephemeral content (system reminders) that shouldn't be persisted
|
|
644
|
-
# - Clear ephemeral content after each LLM call
|
|
645
|
-
# - Add retry logic for transient failures
|
|
646
|
-
def setup_llm_request_hook
|
|
647
|
-
@llm_chat.around_llm_request do |messages, &send_request|
|
|
648
|
-
# Inject ephemeral content (system reminders, etc.)
|
|
649
|
-
# These are sent to LLM but NOT persisted in message history
|
|
650
|
-
prepared_messages = @context_manager.prepare_for_llm(messages)
|
|
651
|
-
|
|
652
|
-
# Make the actual LLM API call with retry logic
|
|
653
|
-
response = call_llm_with_retry do
|
|
654
|
-
send_request.call(prepared_messages)
|
|
655
|
-
end
|
|
656
|
-
|
|
657
|
-
# Clear ephemeral content after successful call
|
|
658
|
-
@context_manager.clear_ephemeral
|
|
659
|
-
|
|
660
|
-
response
|
|
661
|
-
end
|
|
662
|
-
end
|
|
663
|
-
|
|
664
|
-
# --- Semaphore and Reminder Management ---
|
|
665
|
-
|
|
666
|
-
# Execute block with global semaphore
|
|
667
|
-
#
|
|
668
|
-
# @yield Block to execute
|
|
669
|
-
# @return [Object] Result from block
|
|
670
|
-
def execute_with_global_semaphore(&block)
|
|
671
|
-
if @global_semaphore
|
|
672
|
-
@global_semaphore.acquire(&block)
|
|
673
|
-
else
|
|
674
|
-
yield
|
|
675
|
-
end
|
|
676
|
-
end
|
|
677
|
-
|
|
678
|
-
# Check if this is the first user message
|
|
679
|
-
#
|
|
680
|
-
# @return [Boolean] true if no user messages exist yet
|
|
681
|
-
def first_message?
|
|
682
|
-
!has_user_message?
|
|
683
|
-
end
|
|
684
|
-
|
|
685
|
-
# Handle finish markers from hooks
|
|
686
|
-
#
|
|
687
|
-
# @param response [Object] Response from ask (may be a finish marker hash)
|
|
688
|
-
# @return [RubyLLM::Message] Final message
|
|
689
|
-
def handle_finish_marker(response)
|
|
690
|
-
if response.is_a?(Hash)
|
|
691
|
-
if response[:__finish_agent__]
|
|
692
|
-
message = RubyLLM::Message.new(
|
|
693
|
-
role: :assistant,
|
|
694
|
-
content: response[:message],
|
|
695
|
-
model_id: model_id,
|
|
696
|
-
)
|
|
697
|
-
@context_tracker.finish_reason_override = "finish_agent" if @context_tracker
|
|
698
|
-
emit(:end_message, message)
|
|
699
|
-
message
|
|
700
|
-
elsif response[:__finish_swarm__]
|
|
701
|
-
# Propagate finish_swarm marker up
|
|
702
|
-
response
|
|
703
|
-
else
|
|
704
|
-
# Regular response
|
|
705
|
-
response
|
|
706
|
-
end
|
|
707
|
-
else
|
|
708
|
-
response
|
|
709
|
-
end
|
|
710
|
-
end
|
|
711
|
-
|
|
712
|
-
# --- LLM Call Retry Logic ---
|
|
713
|
-
|
|
714
|
-
# Call LLM provider with retry logic for transient failures
|
|
715
|
-
#
|
|
716
|
-
# @param max_retries [Integer] Maximum retry attempts
|
|
717
|
-
# @param delay [Integer] Delay between retries in seconds
|
|
718
|
-
# @yield Block that performs the LLM call
|
|
719
|
-
# @return [Object] Result from block
|
|
720
|
-
def call_llm_with_retry(max_retries: 10, delay: 10, &block)
|
|
721
|
-
attempts = 0
|
|
722
|
-
|
|
723
|
-
loop do
|
|
724
|
-
attempts += 1
|
|
725
|
-
|
|
726
|
-
begin
|
|
727
|
-
return yield
|
|
728
|
-
rescue StandardError => e
|
|
729
|
-
if attempts >= max_retries
|
|
730
|
-
LogStream.emit(
|
|
731
|
-
type: "llm_retry_exhausted",
|
|
732
|
-
agent: @agent_name,
|
|
733
|
-
swarm_id: @agent_context&.swarm_id,
|
|
734
|
-
parent_swarm_id: @agent_context&.parent_swarm_id,
|
|
735
|
-
model: model_id,
|
|
736
|
-
attempts: attempts,
|
|
737
|
-
error_class: e.class.name,
|
|
738
|
-
error_message: e.message,
|
|
739
|
-
error_backtrace: e.backtrace,
|
|
740
|
-
)
|
|
741
|
-
raise
|
|
742
|
-
end
|
|
743
|
-
|
|
744
|
-
LogStream.emit(
|
|
745
|
-
type: "llm_retry_attempt",
|
|
746
|
-
agent: @agent_name,
|
|
747
|
-
swarm_id: @agent_context&.swarm_id,
|
|
748
|
-
parent_swarm_id: @agent_context&.parent_swarm_id,
|
|
749
|
-
model: model_id,
|
|
750
|
-
attempt: attempts,
|
|
751
|
-
max_retries: max_retries,
|
|
752
|
-
error_class: e.class.name,
|
|
753
|
-
error_message: e.message,
|
|
754
|
-
error_backtrace: e.backtrace,
|
|
755
|
-
retry_delay: delay,
|
|
756
|
-
)
|
|
757
|
-
|
|
758
|
-
sleep(delay)
|
|
759
|
-
end
|
|
760
|
-
end
|
|
761
|
-
end
|
|
762
|
-
|
|
763
|
-
# Check if a tool call is a delegation tool
|
|
764
|
-
#
|
|
765
|
-
# @param tool_call [RubyLLM::ToolCall] Tool call to check
|
|
766
|
-
# @return [Boolean] true if this is a delegation tool
|
|
767
|
-
def delegation_tool_call?(tool_call)
|
|
768
|
-
return false unless @agent_context
|
|
769
|
-
|
|
770
|
-
@agent_context.delegation_tool?(tool_call.name)
|
|
771
|
-
end
|
|
772
|
-
end
|
|
773
|
-
end
|
|
774
|
-
end
|