active_agent_rails 0.1.0

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.
@@ -0,0 +1,427 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAgent
4
+ module ChatHelper
5
+ def active_agent_chat_widget(agent:, conversation_id:, title: "AI Assistant", placeholder: "Ask me anything...")
6
+ agent_name = agent.to_s.underscore
7
+ widget_id = "active_agent_#{agent_name}_#{conversation_id.to_s.parameterize.underscore}"
8
+
9
+ raw <<~HTML
10
+ <div id="#{widget_id}" class="active-agent-widget-root">
11
+ <!-- Floating Chat Launcher Button -->
12
+ <button class="active-agent-launcher" onclick="toggleActiveAgentChat('#{widget_id}')">
13
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="active-agent-icon-open">
14
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.58 16.24 3 14.201 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" />
15
+ </svg>
16
+ </button>
17
+
18
+ <!-- Chat Window Box -->
19
+ <div class="active-agent-window hidden">
20
+ <div class="active-agent-header">
21
+ <div class="active-agent-header-info">
22
+ <span class="active-agent-header-title">#{title}</span>
23
+ <span class="active-agent-header-status"><span class="status-dot"></span> Online</span>
24
+ </div>
25
+ <button class="active-agent-close" onclick="toggleActiveAgentChat('#{widget_id}')">&times;</button>
26
+ </div>
27
+
28
+ <div class="active-agent-messages">
29
+ <div class="active-agent-message assistant">
30
+ <div class="message-content">Hello! How can I help you today?</div>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="active-agent-typing hidden">
35
+ <span></span>
36
+ <span></span>
37
+ <span></span>
38
+ </div>
39
+
40
+ <form class="active-agent-form" onsubmit="submitActiveAgentMessage(event, '#{widget_id}', '#{agent_name}', '#{conversation_id}')">
41
+ <input type="text" class="active-agent-input" placeholder="#{placeholder}" autocomplete="off" required />
42
+ <button type="submit" class="active-agent-send">
43
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
44
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" />
45
+ </svg>
46
+ </button>
47
+ </form>
48
+ </div>
49
+ </div>
50
+
51
+ <style>
52
+ .active-agent-widget-root {
53
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
54
+ position: fixed;
55
+ bottom: 24px;
56
+ right: 24px;
57
+ z-index: 9999;
58
+ }
59
+ .active-agent-launcher {
60
+ width: 56px;
61
+ height: 56px;
62
+ border-radius: 28px;
63
+ background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
64
+ color: #ffffff;
65
+ border: none;
66
+ box-shadow: 0 4px 12px rgba(79, 70, 229, 0.4);
67
+ cursor: pointer;
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
72
+ }
73
+ .active-agent-launcher:hover {
74
+ transform: scale(1.08) rotate(5deg);
75
+ box-shadow: 0 6px 16px rgba(79, 70, 229, 0.5);
76
+ }
77
+ .active-agent-icon-open {
78
+ width: 28px;
79
+ height: 28px;
80
+ }
81
+ .active-agent-window {
82
+ position: fixed;
83
+ bottom: 96px;
84
+ right: 24px;
85
+ width: 380px;
86
+ height: 520px;
87
+ max-height: 80vh;
88
+ max-width: 90vw;
89
+ background: rgba(30, 41, 59, 0.95);
90
+ backdrop-filter: blur(12px);
91
+ -webkit-backdrop-filter: blur(12px);
92
+ border: 1px solid rgba(255, 255, 255, 0.1);
93
+ border-radius: 16px;
94
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
95
+ display: flex;
96
+ flex-direction: column;
97
+ overflow: hidden;
98
+ transition: opacity 0.25s ease, transform 0.25s ease;
99
+ }
100
+ .active-agent-window.hidden {
101
+ display: none;
102
+ }
103
+ .active-agent-header {
104
+ background: rgba(15, 23, 42, 0.5);
105
+ padding: 16px;
106
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: center;
110
+ }
111
+ .active-agent-header-info {
112
+ display: flex;
113
+ flex-direction: column;
114
+ }
115
+ .active-agent-header-title {
116
+ color: #ffffff;
117
+ font-weight: 600;
118
+ font-size: 15px;
119
+ }
120
+ .active-agent-header-status {
121
+ font-size: 11px;
122
+ color: #10b981;
123
+ display: flex;
124
+ align-items: center;
125
+ margin-top: 2px;
126
+ }
127
+ .status-dot {
128
+ width: 6px;
129
+ height: 6px;
130
+ background: #10b981;
131
+ border-radius: 50%;
132
+ display: inline-block;
133
+ margin-right: 4px;
134
+ box-shadow: 0 0 6px #10b981;
135
+ }
136
+ .active-agent-close {
137
+ background: transparent;
138
+ border: none;
139
+ color: #94a3b8;
140
+ font-size: 24px;
141
+ cursor: pointer;
142
+ padding: 0;
143
+ line-height: 1;
144
+ transition: color 0.2s;
145
+ }
146
+ .active-agent-close:hover {
147
+ color: #ffffff;
148
+ }
149
+ .active-agent-messages {
150
+ flex: 1;
151
+ padding: 16px;
152
+ overflow-y: auto;
153
+ display: flex;
154
+ flex-direction: column;
155
+ gap: 12px;
156
+ }
157
+ .active-agent-message {
158
+ max-width: 85%;
159
+ padding: 10px 14px;
160
+ border-radius: 12px;
161
+ font-size: 14px;
162
+ line-height: 1.45;
163
+ word-wrap: break-word;
164
+ }
165
+ .active-agent-message.user {
166
+ background: #4f46e5;
167
+ color: #ffffff;
168
+ align-self: flex-end;
169
+ border-bottom-right-radius: 2px;
170
+ box-shadow: 0 2px 6px rgba(79, 70, 229, 0.2);
171
+ }
172
+ .active-agent-message.assistant {
173
+ background: rgba(51, 65, 85, 0.7);
174
+ color: #f1f5f9;
175
+ align-self: flex-start;
176
+ border-bottom-left-radius: 2px;
177
+ border: 1px solid rgba(255, 255, 255, 0.05);
178
+ }
179
+ .active-agent-message code {
180
+ font-family: monospace;
181
+ background: rgba(0,0,0,0.3);
182
+ padding: 2px 4px;
183
+ border-radius: 4px;
184
+ font-size: 12px;
185
+ color: #f472b6;
186
+ }
187
+ .active-agent-message pre {
188
+ background: rgba(0,0,0,0.4);
189
+ padding: 8px;
190
+ border-radius: 6px;
191
+ overflow-x: auto;
192
+ margin: 6px 0;
193
+ }
194
+ .active-agent-message pre code {
195
+ background: transparent;
196
+ padding: 0;
197
+ color: #38bdf8;
198
+ }
199
+ .active-agent-typing {
200
+ align-self: flex-start;
201
+ margin: 0 16px 12px 16px;
202
+ padding: 8px 12px;
203
+ background: rgba(51, 65, 85, 0.5);
204
+ border-radius: 12px;
205
+ display: flex;
206
+ gap: 4px;
207
+ }
208
+ .active-agent-typing.hidden {
209
+ display: none !important;
210
+ }
211
+ .active-agent-typing span {
212
+ width: 6px;
213
+ height: 6px;
214
+ background: #94a3b8;
215
+ border-radius: 50%;
216
+ animation: bounce 1.4s infinite ease-in-out both;
217
+ }
218
+ .active-agent-typing span:nth-child(1) { animation-delay: -0.32s; }
219
+ .active-agent-typing span:nth-child(2) { animation-delay: -0.16s; }
220
+ @keyframes bounce {
221
+ 0%, 80%, 100% { transform: scale(0); }
222
+ 40% { transform: scale(1.0); }
223
+ }
224
+ .active-agent-form {
225
+ padding: 12px;
226
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
227
+ background: rgba(15, 23, 42, 0.4);
228
+ display: flex;
229
+ gap: 8px;
230
+ }
231
+ .active-agent-input {
232
+ flex: 1;
233
+ background: rgba(15, 23, 42, 0.6);
234
+ border: 1px solid rgba(255, 255, 255, 0.1);
235
+ border-radius: 8px;
236
+ color: #ffffff;
237
+ padding: 10px 14px;
238
+ font-size: 13.5px;
239
+ outline: none;
240
+ transition: border-color 0.2s;
241
+ }
242
+ .active-agent-input:focus {
243
+ border-color: #6366f1;
244
+ }
245
+ .active-agent-send {
246
+ background: #4f46e5;
247
+ color: #ffffff;
248
+ border: none;
249
+ width: 38px;
250
+ height: 38px;
251
+ border-radius: 8px;
252
+ cursor: pointer;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: center;
256
+ transition: background-color 0.2s;
257
+ }
258
+ .active-agent-send:hover {
259
+ background: #4338ca;
260
+ }
261
+ .active-agent-send svg {
262
+ width: 18px;
263
+ height: 18px;
264
+ }
265
+ </style>
266
+
267
+ <script>
268
+ function toggleActiveAgentChat(widgetId) {
269
+ const root = document.getElementById(widgetId);
270
+ const windowEl = root.querySelector('.active-agent-window');
271
+ windowEl.classList.toggle('hidden');
272
+ if (!windowEl.classList.contains('hidden')) {
273
+ // Scroll to bottom on open
274
+ const messagesEl = root.querySelector('.active-agent-messages');
275
+ messagesEl.scrollTop = messagesEl.scrollHeight;
276
+
277
+ // Load historical messages from the server
278
+ fetchActiveAgentHistory(widgetId, '#{agent_name}', '#{conversation_id}');
279
+ }
280
+ }
281
+
282
+ let historyLoaded = {};
283
+
284
+ function fetchActiveAgentHistory(widgetId, agentName, conversationId) {
285
+ if (historyLoaded[widgetId]) return;
286
+ const root = document.getElementById(widgetId);
287
+ const messagesEl = root.querySelector('.active-agent-messages');
288
+
289
+ fetch(`/active_agent/chats?agent=${agentName}&conversation_id=${conversationId}`)
290
+ .then(response => response.json())
291
+ .then(data => {
292
+ if (data.messages && data.messages.length > 0) {
293
+ messagesEl.innerHTML = '';
294
+ data.messages.forEach(msg => {
295
+ if (msg.role === 'user' || msg.role === 'assistant') {
296
+ appendActiveAgentMessage(widgetId, msg.content, msg.role);
297
+ }
298
+ });
299
+ messagesEl.scrollTop = messagesEl.scrollHeight;
300
+ historyLoaded[widgetId] = true;
301
+ }
302
+ })
303
+ .catch(err => console.error("Error loading chat history:", err));
304
+ }
305
+
306
+ function appendActiveAgentMessage(widgetId, content, role) {
307
+ const root = document.getElementById(widgetId);
308
+ const messagesEl = root.querySelector('.active-agent-messages');
309
+
310
+ const msgDiv = document.createElement('div');
311
+ msgDiv.className = `active-agent-message ${role}`;
312
+
313
+ // Simple markdown formatter for bold and code blocks
314
+ let formatted = content
315
+ .replace(/&/g, "&amp;")
316
+ .replace(/</g, "&lt;")
317
+ .replace(/>/g, "&gt;")
318
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
319
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
320
+ .replace(/\n/g, '<br/>');
321
+
322
+ msgDiv.innerHTML = `<div class="message-content">${formatted}</div>`;
323
+ messagesEl.appendChild(msgDiv);
324
+ messagesEl.scrollTop = messagesEl.scrollHeight;
325
+ return msgDiv;
326
+ }
327
+
328
+ function submitActiveAgentMessage(event, widgetId, agentName, conversationId) {
329
+ event.preventDefault();
330
+ const root = document.getElementById(widgetId);
331
+ const inputEl = root.querySelector('.active-agent-input');
332
+ const typingEl = root.querySelector('.active-agent-typing');
333
+ const text = inputEl.value.trim();
334
+ if (!text) return;
335
+
336
+ inputEl.value = '';
337
+
338
+ // Add user message
339
+ appendActiveAgentMessage(widgetId, text, 'user');
340
+
341
+ // Show typing indicator
342
+ typingEl.classList.remove('hidden');
343
+
344
+ // Send via fetch SSE
345
+ fetch('/active_agent/chats', {
346
+ method: 'POST',
347
+ headers: {
348
+ 'Content-Type': 'application/x-www-form-urlencoded',
349
+ 'Accept': 'text/event-stream'
350
+ },
351
+ body: new URLSearchParams({
352
+ agent: agentName,
353
+ conversation_id: conversationId,
354
+ message: text
355
+ })
356
+ }).then(response => {
357
+ typingEl.classList.add('hidden');
358
+
359
+ if (!response.body) {
360
+ // Fallback for non-live response
361
+ return response.json().then(data => {
362
+ if (data.reply) {
363
+ appendActiveAgentMessage(widgetId, data.reply, 'assistant');
364
+ } else if (data.error) {
365
+ appendActiveAgentMessage(widgetId, "Error: " + data.error, 'assistant');
366
+ }
367
+ });
368
+ }
369
+
370
+ const reader = response.body.getReader();
371
+ const decoder = new TextDecoder();
372
+ let assistantMsgDiv = null;
373
+ let accumulatedResponse = "";
374
+
375
+ function readStream() {
376
+ reader.read().then(({ done, value }) => {
377
+ if (done) return;
378
+
379
+ const chunkStr = decoder.decode(value, { stream: true });
380
+ const lines = chunkStr.split("\n");
381
+
382
+ lines.forEach(line => {
383
+ if (line.startsWith("data:")) {
384
+ try {
385
+ const data = JSON.parse(line.replace("data:", "").trim());
386
+ if (data.chunk) {
387
+ if (!assistantMsgDiv) {
388
+ assistantMsgDiv = appendActiveAgentMessage(widgetId, "", 'assistant');
389
+ }
390
+ accumulatedResponse += data.chunk;
391
+
392
+ // Update text content with simple formatting
393
+ let formatted = accumulatedResponse
394
+ .replace(/&/g, "&amp;")
395
+ .replace(/</g, "&lt;")
396
+ .replace(/>/g, "&gt;")
397
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
398
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
399
+ .replace(/\n/g, '<br/>');
400
+
401
+ assistantMsgDiv.querySelector('.message-content').innerHTML = formatted;
402
+ const messagesEl = root.querySelector('.active-agent-messages');
403
+ messagesEl.scrollTop = messagesEl.scrollHeight;
404
+ } else if (data.error) {
405
+ appendActiveAgentMessage(widgetId, "Error: " + data.error, 'assistant');
406
+ }
407
+ } catch (e) {
408
+ // Incomplete JSON or other parse error
409
+ }
410
+ }
411
+ });
412
+ readStream();
413
+ });
414
+ }
415
+
416
+ readStream();
417
+ }).catch(err => {
418
+ typingEl.classList.add('hidden');
419
+ appendActiveAgentMessage(widgetId, "Network error: could not connect.", 'assistant');
420
+ console.error(err);
421
+ });
422
+ }
423
+ </script>
424
+ HTML
425
+ end
426
+ end
427
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveAgent::Engine.routes.draw do
4
+ resources :chats, only: [:create, :index]
5
+ delete "chats", to: "chats#destroy"
6
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/class/attribute"
4
+ require_relative "tool"
5
+ require_relative "memory/base"
6
+ require_relative "provider"
7
+
8
+ module ActiveAgent
9
+ class Base
10
+ class_attribute :_provider
11
+ class_attribute :_model
12
+ class_attribute :_system_prompt
13
+ class_attribute :_tools
14
+
15
+ # Initialize class attributes
16
+ self._tools = {}
17
+ self._system_prompt = ""
18
+
19
+ class << self
20
+ def provider(name)
21
+ self._provider = name.to_sym
22
+ end
23
+
24
+ def model(name)
25
+ self._model = name.to_s
26
+ end
27
+
28
+ def system_prompt(prompt)
29
+ self._system_prompt = prompt.to_s
30
+ end
31
+
32
+ def tool(name, description: "", &block)
33
+ # Duplicate to avoid mutating parent class tools
34
+ self._tools = _tools.dup
35
+ self._tools[name.to_s] = Tool.new(name, description: description, &block)
36
+ end
37
+
38
+ def tools
39
+ _tools.values
40
+ end
41
+ end
42
+
43
+ attr_reader :conversation_id, :memory, :provider_client
44
+
45
+ def initialize(conversation_id:, memory_store: nil)
46
+ @conversation_id = conversation_id
47
+
48
+ store = memory_store || ActiveAgent.configuration.memory_store
49
+ @memory = Memory.for(store, conversation_id)
50
+
51
+ provider_name = self.class._provider || ActiveAgent.configuration.default_provider
52
+ model_name = self.class._model
53
+ @provider_client = Provider.for(provider_name, model_name)
54
+ end
55
+
56
+ # Core chat loop that handles automatic tool execution
57
+ def chat(user_input, &block)
58
+ memory.add_message(role: :user, content: user_input)
59
+
60
+ loop_count = 0
61
+ loop do
62
+ loop_count += 1
63
+ if loop_count > 10
64
+ raise "ActiveAgent Loop Limit: Exceeded 10 iterations of tool execution."
65
+ end
66
+
67
+ # Assemble system prompt + chat history
68
+ history = []
69
+ if self.class._system_prompt.present?
70
+ history << { role: :system, content: self.class._system_prompt }
71
+ end
72
+ history += memory.messages
73
+
74
+ # Fetch response from provider (passing block for streaming)
75
+ response = provider_client.chat(history, tools: self.class.tools, &block)
76
+
77
+ # Store response in memory
78
+ memory.add_message(
79
+ role: response[:role],
80
+ content: response[:content],
81
+ tool_calls: response[:tool_calls]
82
+ )
83
+
84
+ if response[:tool_calls]&.any?
85
+ response[:tool_calls].each do |tool_call|
86
+ tool_name = tool_call[:name]
87
+ tool_args = tool_call[:args] || {}
88
+ tool_id = tool_call[:id]
89
+
90
+ ActiveAgent.logger.info("[ActiveAgent] Executing tool '#{tool_name}' with args: #{tool_args}")
91
+
92
+ output_str = if respond_to?(tool_name)
93
+ begin
94
+ # Call the agent instance method
95
+ result = send(tool_name, **tool_args)
96
+ result.is_a?(String) ? result : result.to_json
97
+ rescue StandardError => e
98
+ ActiveAgent.logger.error("[ActiveAgent] Tool '#{tool_name}' error: #{e.message}")
99
+ { error: e.message }.to_json
100
+ end
101
+ else
102
+ ActiveAgent.logger.warn("[ActiveAgent] Tool '#{tool_name}' not implemented on #{self.class.name}")
103
+ { error: "Tool '#{tool_name}' is not implemented on the agent." }.to_json
104
+ end
105
+
106
+ # Store tool response
107
+ memory.add_message(
108
+ role: :tool,
109
+ content: output_str,
110
+ name: tool_name,
111
+ tool_call_id: tool_id
112
+ )
113
+ end
114
+ # Loop again to let LLM process the tool output
115
+ else
116
+ return response[:content]
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module ActiveAgent
6
+ class Configuration
7
+ attr_accessor :gemini_api_key, :openai_api_key, :anthropic_api_key,
8
+ :default_provider, :memory_store, :logger
9
+
10
+ def initialize
11
+ @default_provider = :gemini
12
+ @memory_store = :in_memory
13
+ @logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
14
+ end
15
+ end
16
+
17
+ class << self
18
+ def configuration
19
+ @configuration ||= Configuration.new
20
+ end
21
+
22
+ def configure
23
+ yield(configuration)
24
+ end
25
+
26
+ def logger
27
+ configuration.logger
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/engine"
4
+ require "rails/railtie"
5
+
6
+ module ActiveAgent
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace ActiveAgent
9
+
10
+ initializer "active_agent.helpers" do
11
+ ActiveSupport.on_load(:action_view) do
12
+ include ActiveAgent::ChatHelper
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module ActiveAgent
6
+ module Memory
7
+ # AR Model defined dynamically or referenced if exists
8
+ class MessageRecord < ::ActiveRecord::Base
9
+ self.table_name = "active_agent_messages"
10
+
11
+ # Handle serialization dynamically for compatibility with older/newer Rails
12
+ if respond_to?(:serialize)
13
+ begin
14
+ serialize :tool_calls, coder: JSON
15
+ rescue StandardError
16
+ serialize :tool_calls, JSON
17
+ end
18
+ end
19
+ end
20
+
21
+ class ActiveRecord < Base
22
+ def messages
23
+ ensure_table_exists!
24
+
25
+ MessageRecord.where(conversation_id: conversation_id.to_s)
26
+ .order(:created_at, :id)
27
+ .map do |record|
28
+ msg = {
29
+ role: record.role,
30
+ content: record.content
31
+ }
32
+ if record.tool_calls.present?
33
+ msg[:tool_calls] = record.tool_calls.map { |tc| tc.is_a?(Hash) ? tc.deep_symbolize_keys : tc }
34
+ end
35
+ msg[:tool_call_id] = record.tool_call_id if record.tool_call_id.present?
36
+ msg[:name] = record.name if record.name.present?
37
+ msg
38
+ end
39
+ end
40
+
41
+ def add_message(role:, content:, **options)
42
+ ensure_table_exists!
43
+
44
+ MessageRecord.create!(
45
+ conversation_id: conversation_id.to_s,
46
+ role: role.to_s,
47
+ content: content,
48
+ tool_calls: options[:tool_calls],
49
+ tool_call_id: options[:tool_call_id],
50
+ name: options[:name]
51
+ )
52
+ end
53
+
54
+ def clear
55
+ ensure_table_exists!
56
+ MessageRecord.where(conversation_id: conversation_id.to_s).delete_all
57
+ end
58
+
59
+ private
60
+
61
+ def ensure_table_exists!
62
+ return if ::ActiveRecord::Base.connection.table_exists?("active_agent_messages")
63
+
64
+ raise "active_agent_messages table does not exist. Please run: rails generate active_agent:install && rails db:migrate"
65
+ end
66
+ end
67
+ end
68
+ end