aia 0.9.22 → 0.9.24
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/.version +1 -1
- data/CHANGELOG.md +60 -0
- data/README.md +173 -1
- data/docs/directives-reference.md +28 -8
- data/docs/index.md +2 -2
- data/lib/aia/config/base.rb +79 -0
- data/lib/aia/config/defaults.rb +3 -0
- data/lib/aia/directives/checkpoint.rb +283 -0
- data/lib/aia/directives/configuration.rb +3 -88
- data/lib/aia/directives/models.rb +12 -5
- data/lib/aia/directives/registry.rb +2 -0
- data/lib/aia/directives/utility.rb +23 -8
- data/lib/aia/ruby_llm_adapter.rb +20 -4
- data/lib/aia/session.rb +40 -148
- data/lib/aia/topic_context.rb +125 -0
- data/lib/aia/utility.rb +12 -1
- data/lib/extensions/openstruct_merge.rb +4 -0
- metadata +4 -3
- data/lib/aia/context_manager.rb +0 -134
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# lib/aia/directives/checkpoint.rb
|
|
2
|
+
#
|
|
3
|
+
# Checkpoint and restore directives for managing conversation state.
|
|
4
|
+
# Uses RubyLLM's Chat.@messages as the source of truth for conversation history.
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
module AIA
|
|
8
|
+
module Directives
|
|
9
|
+
module Checkpoint
|
|
10
|
+
# Module-level state for checkpoints
|
|
11
|
+
# Note: Using @checkpoint_store to avoid naming conflict with //checkpoints directive
|
|
12
|
+
@checkpoint_store = {}
|
|
13
|
+
@checkpoint_counter = 0
|
|
14
|
+
@last_checkpoint_name = nil
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
attr_accessor :checkpoint_store, :checkpoint_counter, :last_checkpoint_name
|
|
18
|
+
|
|
19
|
+
# Reset all checkpoint state (useful for testing)
|
|
20
|
+
def reset!
|
|
21
|
+
@checkpoint_store = {}
|
|
22
|
+
@checkpoint_counter = 0
|
|
23
|
+
@last_checkpoint_name = nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# //checkpoint [name]
|
|
28
|
+
# Creates a named checkpoint of the current conversation state.
|
|
29
|
+
# If no name is provided, uses an auto-incrementing number.
|
|
30
|
+
def self.checkpoint(args, _unused = nil)
|
|
31
|
+
name = args.empty? ? nil : args.join(' ').strip
|
|
32
|
+
|
|
33
|
+
if name.nil? || name.empty?
|
|
34
|
+
self.checkpoint_counter += 1
|
|
35
|
+
name = checkpoint_counter.to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
chats = get_chats
|
|
39
|
+
return "Error: No active chat sessions found." if chats.nil? || chats.empty?
|
|
40
|
+
|
|
41
|
+
# Deep copy messages from all chats
|
|
42
|
+
first_chat_messages = chats.values.first&.messages || []
|
|
43
|
+
checkpoint_store[name] = {
|
|
44
|
+
messages: chats.transform_values { |chat|
|
|
45
|
+
chat.messages.map { |msg| deep_copy_message(msg) }
|
|
46
|
+
},
|
|
47
|
+
position: first_chat_messages.size,
|
|
48
|
+
created_at: Time.now,
|
|
49
|
+
topic_preview: extract_last_user_message(first_chat_messages)
|
|
50
|
+
}
|
|
51
|
+
self.last_checkpoint_name = name
|
|
52
|
+
|
|
53
|
+
puts "Checkpoint '#{name}' created at position #{checkpoint_store[name][:position]}."
|
|
54
|
+
""
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# //restore [name]
|
|
58
|
+
# Restores the conversation state to a previously saved checkpoint.
|
|
59
|
+
# If no name is provided, restores to the previous checkpoint (one step back).
|
|
60
|
+
def self.restore(args, _unused = nil)
|
|
61
|
+
name = args.empty? ? nil : args.join(' ').strip
|
|
62
|
+
|
|
63
|
+
if name.nil? || name.empty?
|
|
64
|
+
# Find the previous checkpoint (second-to-last by position)
|
|
65
|
+
name = find_previous_checkpoint
|
|
66
|
+
if name.nil?
|
|
67
|
+
return "Error: No previous checkpoint to restore to."
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
unless checkpoint_store.key?(name)
|
|
72
|
+
available = checkpoint_names.empty? ? "none" : checkpoint_names.join(', ')
|
|
73
|
+
return "Error: Checkpoint '#{name}' not found. Available: #{available}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
checkpoint_data = checkpoint_store[name]
|
|
77
|
+
chats = get_chats
|
|
78
|
+
|
|
79
|
+
return "Error: No active chat sessions found." if chats.nil? || chats.empty?
|
|
80
|
+
|
|
81
|
+
# Restore messages to each chat
|
|
82
|
+
checkpoint_data[:messages].each do |model_id, saved_messages|
|
|
83
|
+
chat = chats[model_id]
|
|
84
|
+
next unless chat
|
|
85
|
+
|
|
86
|
+
# Replace the chat's messages with the saved ones
|
|
87
|
+
restored_messages = saved_messages.map { |msg| deep_copy_message(msg) }
|
|
88
|
+
chat.instance_variable_set(:@messages, restored_messages)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Remove checkpoints that are now invalid (position > restored position)
|
|
92
|
+
# because they reference conversation states that no longer exist
|
|
93
|
+
restored_position = checkpoint_data[:position]
|
|
94
|
+
removed_count = remove_invalid_checkpoints(restored_position)
|
|
95
|
+
|
|
96
|
+
# Update last_checkpoint_name to this checkpoint
|
|
97
|
+
self.last_checkpoint_name = name
|
|
98
|
+
|
|
99
|
+
msg = "Context restored to checkpoint '#{name}' (position #{restored_position})."
|
|
100
|
+
msg += " Removed #{removed_count} checkpoint(s) that were beyond this position." if removed_count > 0
|
|
101
|
+
msg
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# //clear
|
|
105
|
+
# Clears the conversation context, optionally keeping the system prompt.
|
|
106
|
+
def self.clear(args, _unused = nil)
|
|
107
|
+
keep_system = !args.include?('--all')
|
|
108
|
+
|
|
109
|
+
chats = get_chats
|
|
110
|
+
return "Error: No active chat sessions found." if chats.nil? || chats.empty?
|
|
111
|
+
|
|
112
|
+
chats.each do |_model_id, chat|
|
|
113
|
+
if keep_system
|
|
114
|
+
system_msg = chat.messages.find { |m| m.role == :system }
|
|
115
|
+
chat.instance_variable_set(:@messages, [])
|
|
116
|
+
chat.add_message(system_msg) if system_msg
|
|
117
|
+
else
|
|
118
|
+
chat.instance_variable_set(:@messages, [])
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Clear all checkpoints
|
|
123
|
+
checkpoint_store.clear
|
|
124
|
+
self.checkpoint_counter = 0
|
|
125
|
+
self.last_checkpoint_name = nil
|
|
126
|
+
|
|
127
|
+
"Chat context cleared."
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# //review
|
|
131
|
+
# Displays the current conversation context with checkpoint markers.
|
|
132
|
+
def self.review(args, _unused = nil)
|
|
133
|
+
chats = get_chats
|
|
134
|
+
return "Error: No active chat sessions found." if chats.nil? || chats.empty?
|
|
135
|
+
|
|
136
|
+
# For multi-model, show first chat's messages (they should be similar for user messages)
|
|
137
|
+
first_chat = chats.values.first
|
|
138
|
+
messages = first_chat&.messages || []
|
|
139
|
+
|
|
140
|
+
puts "\n=== Chat Context (RubyLLM) ==="
|
|
141
|
+
puts "Total messages: #{messages.size}"
|
|
142
|
+
puts "Models: #{chats.keys.join(', ')}"
|
|
143
|
+
puts "Checkpoints: #{checkpoint_names.join(', ')}" if checkpoint_names.any?
|
|
144
|
+
puts
|
|
145
|
+
|
|
146
|
+
positions = checkpoint_positions
|
|
147
|
+
|
|
148
|
+
messages.each_with_index do |msg, index|
|
|
149
|
+
# Show checkpoint marker if one exists at this position
|
|
150
|
+
if positions[index]
|
|
151
|
+
puts "📍 [Checkpoint: #{positions[index].join(', ')}]"
|
|
152
|
+
puts "-" * 40
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
role = msg.role.to_s.capitalize
|
|
156
|
+
content = format_message_content(msg)
|
|
157
|
+
|
|
158
|
+
puts "#{index + 1}. [#{role}]: #{content}"
|
|
159
|
+
puts
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Check for checkpoint at the end
|
|
163
|
+
if positions[messages.size]
|
|
164
|
+
puts "📍 [Checkpoint: #{positions[messages.size].join(', ')}]"
|
|
165
|
+
puts "-" * 40
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
puts "=== End of Context ==="
|
|
169
|
+
""
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# //checkpoints
|
|
173
|
+
# Lists all available checkpoints with their details.
|
|
174
|
+
def self.checkpoints_list(args, _unused = nil)
|
|
175
|
+
if checkpoint_store.empty?
|
|
176
|
+
puts "No checkpoints available."
|
|
177
|
+
return ""
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
puts "\n=== Available Checkpoints ==="
|
|
181
|
+
checkpoint_store.each do |name, data|
|
|
182
|
+
created = data[:created_at]&.strftime('%H:%M:%S') || 'unknown'
|
|
183
|
+
puts " #{name}: position #{data[:position]}, created #{created}"
|
|
184
|
+
if data[:topic_preview] && !data[:topic_preview].empty?
|
|
185
|
+
puts " → \"#{data[:topic_preview]}\""
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
puts "=== End of Checkpoints ==="
|
|
189
|
+
""
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Helper methods
|
|
193
|
+
def self.checkpoint_names
|
|
194
|
+
checkpoint_store.keys
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def self.checkpoint_positions
|
|
198
|
+
positions = {}
|
|
199
|
+
checkpoint_store.each do |name, data|
|
|
200
|
+
pos = data[:position]
|
|
201
|
+
positions[pos] ||= []
|
|
202
|
+
positions[pos] << name
|
|
203
|
+
end
|
|
204
|
+
positions
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Remove checkpoints with position > the given position
|
|
208
|
+
# Returns the count of removed checkpoints
|
|
209
|
+
def self.remove_invalid_checkpoints(max_position)
|
|
210
|
+
invalid_names = checkpoint_store.select { |_name, data| data[:position] > max_position }.keys
|
|
211
|
+
invalid_names.each { |name| checkpoint_store.delete(name) }
|
|
212
|
+
invalid_names.size
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Find the previous checkpoint (second-to-last by position)
|
|
216
|
+
# This is used when //restore is called without a name
|
|
217
|
+
def self.find_previous_checkpoint
|
|
218
|
+
return nil if checkpoint_store.size < 2
|
|
219
|
+
|
|
220
|
+
# Sort checkpoints by position descending, return the second one
|
|
221
|
+
sorted = checkpoint_store.sort_by { |_name, data| -data[:position] }
|
|
222
|
+
sorted[1]&.first # Return the name of the second-to-last checkpoint
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
def self.get_chats
|
|
228
|
+
return nil unless AIA.client.respond_to?(:chats)
|
|
229
|
+
|
|
230
|
+
AIA.client.chats
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def self.deep_copy_message(msg)
|
|
234
|
+
RubyLLM::Message.new(
|
|
235
|
+
role: msg.role,
|
|
236
|
+
content: msg.content,
|
|
237
|
+
tool_calls: msg.tool_calls&.transform_values { |tc| tc.dup rescue tc },
|
|
238
|
+
tool_call_id: msg.tool_call_id,
|
|
239
|
+
input_tokens: msg.input_tokens,
|
|
240
|
+
output_tokens: msg.output_tokens,
|
|
241
|
+
model_id: msg.model_id
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def self.format_message_content(msg)
|
|
246
|
+
if msg.tool_call?
|
|
247
|
+
tool_names = msg.tool_calls.values.map(&:name).join(', ')
|
|
248
|
+
"[Tool calls: #{tool_names}]"
|
|
249
|
+
elsif msg.tool_result?
|
|
250
|
+
result_preview = msg.content.to_s[0..50]
|
|
251
|
+
"[Tool result for: #{msg.tool_call_id}] #{result_preview}..."
|
|
252
|
+
else
|
|
253
|
+
content = msg.content.to_s
|
|
254
|
+
content.length > 150 ? "#{content[0..147]}..." : content
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Extract a preview of the last user message for checkpoint context
|
|
259
|
+
def self.extract_last_user_message(messages, max_length: 70)
|
|
260
|
+
return "" if messages.nil? || messages.empty?
|
|
261
|
+
|
|
262
|
+
# Find the last user message (not system, not assistant, not tool)
|
|
263
|
+
last_user_msg = messages.reverse.find { |msg| msg.role == :user }
|
|
264
|
+
return "" unless last_user_msg
|
|
265
|
+
|
|
266
|
+
content = last_user_msg.content.to_s.strip
|
|
267
|
+
# Collapse whitespace and truncate
|
|
268
|
+
content = content.gsub(/\s+/, ' ')
|
|
269
|
+
content.length > max_length ? "#{content[0..max_length - 4]}..." : content
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Aliases
|
|
273
|
+
class << self
|
|
274
|
+
alias_method :ckp, :checkpoint
|
|
275
|
+
alias_method :cp, :checkpoint
|
|
276
|
+
alias_method :context, :review
|
|
277
|
+
# Route //checkpoints directive to checkpoints_list
|
|
278
|
+
# (safe now that we renamed attr_accessor to checkpoint_store)
|
|
279
|
+
alias_method :checkpoints, :checkpoints_list
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -88,100 +88,15 @@ module AIA
|
|
|
88
88
|
send(:config, args.prepend('top_p'), context_manager)
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
context_manager.clear_context
|
|
97
|
-
''
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def self.review(args, context_manager = nil)
|
|
101
|
-
return "Error: Context manager not available for //review directive." if context_manager.nil?
|
|
102
|
-
|
|
103
|
-
context = context_manager.get_context
|
|
104
|
-
checkpoint_positions = context_manager.checkpoint_positions
|
|
105
|
-
|
|
106
|
-
# Display context with checkpoint markers
|
|
107
|
-
puts "\n=== Chat Context ==="
|
|
108
|
-
puts "Total messages: #{context.size}"
|
|
109
|
-
|
|
110
|
-
if checkpoint_positions.any?
|
|
111
|
-
puts "Checkpoints: #{context_manager.checkpoint_names.join(', ')}"
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
puts "\n"
|
|
115
|
-
|
|
116
|
-
context.each_with_index do |message, index|
|
|
117
|
-
# Check if there's a checkpoint at this position
|
|
118
|
-
if checkpoint_positions[index]
|
|
119
|
-
checkpoint_names = checkpoint_positions[index].join(', ')
|
|
120
|
-
puts "📍 [Checkpoint: #{checkpoint_names}]"
|
|
121
|
-
puts "-" * 40
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Display the message
|
|
125
|
-
role_display = message[:role].capitalize
|
|
126
|
-
content_preview = message[:content].to_s
|
|
127
|
-
|
|
128
|
-
# Truncate long content for display
|
|
129
|
-
if content_preview.length > 200
|
|
130
|
-
content_preview = content_preview[0..197] + "..."
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
puts "#{index + 1}. [#{role_display}]: #{content_preview}"
|
|
134
|
-
puts ""
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Check if there's a checkpoint at the end (after all messages)
|
|
138
|
-
if checkpoint_positions[context.size]
|
|
139
|
-
checkpoint_names = checkpoint_positions[context.size].join(', ')
|
|
140
|
-
puts "📍 [Checkpoint: #{checkpoint_names}]"
|
|
141
|
-
puts "-" * 40
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
puts "=== End of Context ==="
|
|
145
|
-
''
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def self.checkpoint(args, context_manager = nil)
|
|
149
|
-
if context_manager.nil?
|
|
150
|
-
return "Error: Context manager not available for //checkpoint directive."
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
name = args.empty? ? nil : args.join(' ').strip
|
|
154
|
-
checkpoint_name = context_manager.create_checkpoint(name: name)
|
|
155
|
-
puts "Checkpoint '#{checkpoint_name}' created."
|
|
156
|
-
""
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def self.restore(args, context_manager = nil)
|
|
160
|
-
if context_manager.nil?
|
|
161
|
-
return "Error: Context manager not available for //restore directive."
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
name = args.empty? ? nil : args.join(' ').strip
|
|
165
|
-
|
|
166
|
-
if context_manager.restore_checkpoint(name: name)
|
|
167
|
-
restored_name = name || context_manager.checkpoint_names.last
|
|
168
|
-
"Context restored to checkpoint '#{restored_name}'."
|
|
169
|
-
else
|
|
170
|
-
if name
|
|
171
|
-
"Error: Checkpoint '#{name}' not found. Available checkpoints: #{context_manager.checkpoint_names.join(', ')}"
|
|
172
|
-
else
|
|
173
|
-
"Error: No checkpoints available to restore."
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
end
|
|
91
|
+
# NOTE: clear, review, checkpoint, and restore directives have been moved to
|
|
92
|
+
# lib/aia/directives/checkpoint.rb which uses RubyLLM's Chat.@messages
|
|
93
|
+
# as the source of truth for conversation history.
|
|
177
94
|
|
|
178
95
|
# Set up aliases - these work on the module's singleton class
|
|
179
96
|
class << self
|
|
180
97
|
alias_method :cfg, :config
|
|
181
98
|
alias_method :temp, :temperature
|
|
182
99
|
alias_method :topp, :top_p
|
|
183
|
-
alias_method :context, :review
|
|
184
|
-
alias_method :ckp, :checkpoint
|
|
185
100
|
end
|
|
186
101
|
end
|
|
187
102
|
end
|
|
@@ -231,9 +231,10 @@ module AIA
|
|
|
231
231
|
'review' => 'Display the current conversation context with checkpoint markers',
|
|
232
232
|
'checkpoint' => 'Create a named checkpoint of the current context',
|
|
233
233
|
'restore' => 'Restore context to a previous checkpoint',
|
|
234
|
+
'checkpoints_list' => 'List all available checkpoints',
|
|
234
235
|
|
|
235
236
|
# Utility directives
|
|
236
|
-
'tools' => 'List available tools',
|
|
237
|
+
'tools' => 'List available tools (optional filter by name substring)',
|
|
237
238
|
'next' => 'Set the next prompt in the sequence',
|
|
238
239
|
'pipeline' => 'Set or view the prompt workflow sequence',
|
|
239
240
|
'terse' => 'Add instruction for concise responses',
|
|
@@ -261,6 +262,7 @@ module AIA
|
|
|
261
262
|
'temp' => nil, # alias for temperature
|
|
262
263
|
'topp' => nil, # alias for top_p
|
|
263
264
|
'context' => nil, # alias for review
|
|
265
|
+
'ckp' => nil, # alias for checkpoint
|
|
264
266
|
'cp' => nil, # alias for checkpoint
|
|
265
267
|
'workflow' => nil, # alias for pipeline
|
|
266
268
|
'rb' => nil, # alias for ruby
|
|
@@ -282,13 +284,18 @@ module AIA
|
|
|
282
284
|
AIA::Directives::Utility,
|
|
283
285
|
AIA::Directives::Configuration,
|
|
284
286
|
AIA::Directives::Execution,
|
|
285
|
-
AIA::Directives::Models
|
|
287
|
+
AIA::Directives::Models,
|
|
288
|
+
AIA::Directives::Checkpoint
|
|
286
289
|
]
|
|
287
290
|
|
|
288
291
|
all_directives = {}
|
|
289
292
|
excluded_methods = ['run', 'initialize', 'private?', 'descriptions', 'aliases', 'build_aliases',
|
|
290
293
|
'desc', 'method_added', 'register_directive_module', 'process',
|
|
291
|
-
'directive?', 'prefix_size'
|
|
294
|
+
'directive?', 'prefix_size', 'reset!', 'checkpoint_names',
|
|
295
|
+
'checkpoint_positions', 'get_chats', 'deep_copy_message',
|
|
296
|
+
'format_message_content', 'checkpoints', 'checkpoint_counter',
|
|
297
|
+
'last_checkpoint_name', 'checkpoints=', 'checkpoint_counter=',
|
|
298
|
+
'last_checkpoint_name=']
|
|
292
299
|
|
|
293
300
|
# Collect directives from all modules
|
|
294
301
|
all_modules.each do |mod|
|
|
@@ -314,7 +321,7 @@ module AIA
|
|
|
314
321
|
'temperature' => ['temp'],
|
|
315
322
|
'top_p' => ['topp'],
|
|
316
323
|
'review' => ['context'],
|
|
317
|
-
'checkpoint' => ['cp'],
|
|
324
|
+
'checkpoint' => ['ckp', 'cp'],
|
|
318
325
|
'pipeline' => ['workflow'],
|
|
319
326
|
'ruby' => ['rb'],
|
|
320
327
|
'shell' => ['sh'],
|
|
@@ -333,7 +340,7 @@ module AIA
|
|
|
333
340
|
|
|
334
341
|
# Sort and display directives by category
|
|
335
342
|
categories = {
|
|
336
|
-
'Configuration' => ['config', 'model', 'temperature', 'top_p', 'clear', 'review', 'checkpoint', 'restore'],
|
|
343
|
+
'Configuration' => ['config', 'model', 'temperature', 'top_p', 'clear', 'review', 'checkpoint', 'restore', 'checkpoints_list'],
|
|
337
344
|
'Utility' => ['tools', 'next', 'pipeline', 'terse', 'robot', 'help'],
|
|
338
345
|
'Execution' => ['ruby', 'shell', 'say'],
|
|
339
346
|
'Web & Files' => ['webpage', 'include'],
|
|
@@ -5,6 +5,7 @@ require_relative 'utility'
|
|
|
5
5
|
require_relative 'configuration'
|
|
6
6
|
require_relative 'execution'
|
|
7
7
|
require_relative 'models'
|
|
8
|
+
require_relative 'checkpoint'
|
|
8
9
|
|
|
9
10
|
module AIA
|
|
10
11
|
module Directives
|
|
@@ -115,6 +116,7 @@ module AIA
|
|
|
115
116
|
register_directive_module(Configuration)
|
|
116
117
|
register_directive_module(Execution)
|
|
117
118
|
register_directive_module(Models)
|
|
119
|
+
register_directive_module(Checkpoint)
|
|
118
120
|
end
|
|
119
121
|
end
|
|
120
122
|
end
|
|
@@ -12,19 +12,34 @@ module AIA
|
|
|
12
12
|
indent = 4
|
|
13
13
|
spaces = " " * indent
|
|
14
14
|
width = TTY::Screen.width - indent - 2
|
|
15
|
+
filter = args.first&.downcase
|
|
15
16
|
|
|
16
17
|
if AIA.config.tools.empty?
|
|
17
18
|
puts "No tools are available"
|
|
18
19
|
else
|
|
19
|
-
|
|
20
|
-
puts "Available Tools"
|
|
21
|
-
puts "==============="
|
|
20
|
+
tools_to_display = AIA.config.tools
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
if filter
|
|
23
|
+
tools_to_display = tools_to_display.select do |tool|
|
|
24
|
+
name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
|
25
|
+
name.downcase.include?(filter)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if tools_to_display.empty?
|
|
30
|
+
puts "No tools match the filter: #{args.first}"
|
|
31
|
+
else
|
|
32
|
+
puts
|
|
33
|
+
header = filter ? "Available Tools (filtered by '#{args.first}')" : "Available Tools"
|
|
34
|
+
puts header
|
|
35
|
+
puts "=" * header.length
|
|
36
|
+
|
|
37
|
+
tools_to_display.each do |tool|
|
|
38
|
+
name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
|
39
|
+
puts "\n#{name}"
|
|
40
|
+
puts "-" * name.size
|
|
41
|
+
puts WordWrapper::MinimumRaggedness.new(width, tool.description).wrap.split("\n").map { |s| spaces + s + "\n" }.join
|
|
42
|
+
end
|
|
28
43
|
end
|
|
29
44
|
end
|
|
30
45
|
puts
|
data/lib/aia/ruby_llm_adapter.rb
CHANGED
|
@@ -5,7 +5,7 @@ require_relative '../extensions/ruby_llm/provider_fix'
|
|
|
5
5
|
|
|
6
6
|
module AIA
|
|
7
7
|
class RubyLLMAdapter
|
|
8
|
-
attr_reader :tools, :model_specs
|
|
8
|
+
attr_reader :tools, :model_specs, :chats
|
|
9
9
|
|
|
10
10
|
def initialize
|
|
11
11
|
@model_specs = extract_models_config # Full specs with role info
|
|
@@ -228,7 +228,17 @@ module AIA
|
|
|
228
228
|
|
|
229
229
|
def support_local_tools
|
|
230
230
|
@tools += ObjectSpace.each_object(Class).select do |klass|
|
|
231
|
-
klass < RubyLLM::Tool
|
|
231
|
+
next false unless klass < RubyLLM::Tool
|
|
232
|
+
|
|
233
|
+
# Filter out tools that can't be instantiated without arguments
|
|
234
|
+
# RubyLLM calls tool.new without args, so we must verify each tool works
|
|
235
|
+
begin
|
|
236
|
+
klass.new
|
|
237
|
+
true
|
|
238
|
+
rescue ArgumentError, LoadError, StandardError
|
|
239
|
+
# Skip tools that require arguments or have missing dependencies
|
|
240
|
+
false
|
|
241
|
+
end
|
|
232
242
|
end
|
|
233
243
|
end
|
|
234
244
|
|
|
@@ -634,7 +644,10 @@ module AIA
|
|
|
634
644
|
|
|
635
645
|
@tools.select! do |tool|
|
|
636
646
|
tool_name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
|
637
|
-
AIA.config.allowed_tools
|
|
647
|
+
AIA.config.allowed_tools
|
|
648
|
+
.split(',')
|
|
649
|
+
.map(&:strip)
|
|
650
|
+
.any? { |allowed| tool_name.include?(allowed) }
|
|
638
651
|
end
|
|
639
652
|
end
|
|
640
653
|
|
|
@@ -644,7 +657,10 @@ module AIA
|
|
|
644
657
|
|
|
645
658
|
@tools.reject! do |tool|
|
|
646
659
|
tool_name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
|
647
|
-
AIA.config.rejected_tools
|
|
660
|
+
AIA.config.rejected_tools
|
|
661
|
+
.split(',')
|
|
662
|
+
.map(&:strip)
|
|
663
|
+
.any? { |rejected| tool_name.include?(rejected) }
|
|
648
664
|
end
|
|
649
665
|
end
|
|
650
666
|
|