rails_console_ai 0.21.0 → 0.22.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/rails_console_ai/context_builder.rb +0 -3
- data/lib/rails_console_ai/conversation_engine.rb +97 -14
- data/lib/rails_console_ai/providers/bedrock.rb +2 -0
- data/lib/rails_console_ai/tools/registry.rb +14 -1
- data/lib/rails_console_ai/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dba28b6d7543df66792877bddc51336f105c441a06b089de34ab0c7f89ca8f26
|
|
4
|
+
data.tar.gz: 4a54272fafd704abd003f06f2ab065d11f47b7c9a42b2f7c99c55b07b8712f8a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27c8413cc63c84c94d0fad464c3f3d6cd0b376a744122a2358b5899e0382f82e696a3e188647db952235986ee2a973c85b73065939a898cd7229955a61c377cf
|
|
7
|
+
data.tar.gz: d7e3041e7e37dd2ed304537bdfb02693647d3d32a9f1b6888718d9e38fcafe0c1b92000606c970716cd5074e7424b81e37d894d099b93ac6155268703ad54738
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.22.0]
|
|
6
|
+
|
|
7
|
+
- Fix blank content block handling in Bedrock provider
|
|
8
|
+
- Fix `recall_output` to expand in place and restore preview after LLM responds, preventing context bloat
|
|
9
|
+
- Remove safety guard bypass from prompt
|
|
10
|
+
- Fix issue where LLM couldn't recall multiple outputs at once
|
|
11
|
+
|
|
5
12
|
## [0.21.0]
|
|
6
13
|
|
|
7
14
|
- Add Slack @mention support and channel name tracking
|
|
@@ -82,9 +82,6 @@ module RailsConsoleAi
|
|
|
82
82
|
(e.g. `api = SalesforceApi.new(step1)`).
|
|
83
83
|
- If the user asks you to provide code for them to run later (not execute now), put it
|
|
84
84
|
in a ```ruby code block in your text response.
|
|
85
|
-
- Use `RailsConsoleAi.configuration.safety_guards.without_guards { }` to wrap any
|
|
86
|
-
operation that should bypass safety guards (e.g. calling a known-safe admin method).
|
|
87
|
-
|
|
88
85
|
You have skills — predefined procedures for specific operations. When a user's request
|
|
89
86
|
matches a skill, call activate_skill first to load the recipe and enable its guard
|
|
90
87
|
bypasses, then follow the recipe.
|
|
@@ -242,11 +242,16 @@ module RailsConsoleAi
|
|
|
242
242
|
output_parts << "Return value: #{exec_result.inspect}" if exec_result
|
|
243
243
|
|
|
244
244
|
result_str = output_parts.join("\n\n")
|
|
245
|
-
result_str = result_str[0..1000] + '...' if result_str.length > 1000
|
|
246
245
|
|
|
247
246
|
context_msg = "User directly executed code: `#{raw_code}`"
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
if result_str.length > LARGE_OUTPUT_THRESHOLD
|
|
248
|
+
output_id = @executor.store_output(result_str)
|
|
249
|
+
preview = result_str[0, LARGE_OUTPUT_PREVIEW_CHARS]
|
|
250
|
+
context_msg += "\n#{preview}\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{result_str.length} chars — use recall_output tool with id #{output_id} to retrieve the full output]"
|
|
251
|
+
elsif !output_parts.empty?
|
|
252
|
+
output_id = @executor.store_output(result_str)
|
|
253
|
+
context_msg += "\n#{result_str}"
|
|
254
|
+
end
|
|
250
255
|
@history << { role: :user, content: context_msg, output_id: output_id }
|
|
251
256
|
|
|
252
257
|
@interactive_query ||= "> #{raw_code}"
|
|
@@ -314,9 +319,15 @@ module RailsConsoleAi
|
|
|
314
319
|
output_parts << "Return value: #{exec_result.inspect}" if exec_result
|
|
315
320
|
unless output_parts.empty?
|
|
316
321
|
result_str = output_parts.join("\n\n")
|
|
317
|
-
result_str = result_str[0..1000] + '...' if result_str.length > 1000
|
|
318
322
|
output_id = @executor.store_output(result_str)
|
|
319
|
-
|
|
323
|
+
context_msg = "Code was executed (safety override). "
|
|
324
|
+
if result_str.length > LARGE_OUTPUT_THRESHOLD
|
|
325
|
+
context_msg += result_str[0, LARGE_OUTPUT_PREVIEW_CHARS]
|
|
326
|
+
context_msg += "\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{result_str.length} chars — use recall_output tool with id #{output_id} to retrieve the full output]"
|
|
327
|
+
else
|
|
328
|
+
context_msg += result_str
|
|
329
|
+
end
|
|
330
|
+
@history << { role: :user, content: context_msg, output_id: output_id }
|
|
320
331
|
end
|
|
321
332
|
:success
|
|
322
333
|
else
|
|
@@ -336,9 +347,15 @@ module RailsConsoleAi
|
|
|
336
347
|
|
|
337
348
|
unless output_parts.empty?
|
|
338
349
|
result_str = output_parts.join("\n\n")
|
|
339
|
-
result_str = result_str[0..1000] + '...' if result_str.length > 1000
|
|
340
350
|
output_id = @executor.store_output(result_str)
|
|
341
|
-
|
|
351
|
+
context_msg = "Code was executed. "
|
|
352
|
+
if result_str.length > LARGE_OUTPUT_THRESHOLD
|
|
353
|
+
context_msg += result_str[0, LARGE_OUTPUT_PREVIEW_CHARS]
|
|
354
|
+
context_msg += "\n\n[Output truncated at #{LARGE_OUTPUT_PREVIEW_CHARS} of #{result_str.length} chars — use recall_output tool with id #{output_id} to retrieve the full output]"
|
|
355
|
+
else
|
|
356
|
+
context_msg += result_str
|
|
357
|
+
end
|
|
358
|
+
@history << { role: :user, content: context_msg, output_id: output_id }
|
|
342
359
|
end
|
|
343
360
|
|
|
344
361
|
:success
|
|
@@ -615,6 +632,8 @@ module RailsConsoleAi
|
|
|
615
632
|
|
|
616
633
|
This session has safety guards that block side effects (database writes, HTTP mutations, etc.).
|
|
617
634
|
If an operation is blocked, the user will be prompted to allow it or disable guards.
|
|
635
|
+
Do NOT attempt to bypass or work around safety guards in your code — just write the
|
|
636
|
+
operation normally and let the safety system handle it.
|
|
618
637
|
PROMPT
|
|
619
638
|
end
|
|
620
639
|
end
|
|
@@ -803,6 +822,28 @@ module RailsConsoleAi
|
|
|
803
822
|
last_tool_names = result.tool_calls.map { |tc| tc[:name] }
|
|
804
823
|
result.tool_calls.each do |tc|
|
|
805
824
|
break if @channel.cancelled?
|
|
825
|
+
|
|
826
|
+
# Intercept recall_output/recall_outputs: expand in place instead of adding large messages
|
|
827
|
+
if tc[:name] == 'recall_output' || tc[:name] == 'recall_outputs'
|
|
828
|
+
ids = if tc[:name] == 'recall_outputs'
|
|
829
|
+
Array(tc[:arguments]['ids']).map(&:to_i)
|
|
830
|
+
else
|
|
831
|
+
[tc[:arguments]['id'].to_i]
|
|
832
|
+
end
|
|
833
|
+
@channel.display_tool_call("#{tc[:name]}(#{ids.join(', ')})")
|
|
834
|
+
expanded = expand_outputs_in_place(messages, ids)
|
|
835
|
+
tool_result = if expanded.any?
|
|
836
|
+
"Expanded #{expanded.length} output(s) in conversation. The full content is now visible in the original message(s) above."
|
|
837
|
+
else
|
|
838
|
+
"No matching outputs found with id(s) #{ids.join(', ')}."
|
|
839
|
+
end
|
|
840
|
+
@channel.display_dim(" #{tool_result}")
|
|
841
|
+
tool_msg = provider.format_tool_result(tc[:id], tool_result)
|
|
842
|
+
messages << tool_msg
|
|
843
|
+
new_messages << tool_msg
|
|
844
|
+
next
|
|
845
|
+
end
|
|
846
|
+
|
|
806
847
|
if tc[:name] == 'ask_user' || tc[:name] == 'execute_plan'
|
|
807
848
|
# Display any pending LLM text before prompting the user
|
|
808
849
|
if last_thinking
|
|
@@ -848,6 +889,10 @@ module RailsConsoleAi
|
|
|
848
889
|
exhausted = true if round == max_rounds - 1
|
|
849
890
|
end
|
|
850
891
|
|
|
892
|
+
# Re-truncate any outputs that were expanded for the LLM — the LLM has
|
|
893
|
+
# seen them and responded, so collapse back to save context on future calls.
|
|
894
|
+
re_truncate_expanded(messages)
|
|
895
|
+
|
|
851
896
|
if exhausted
|
|
852
897
|
$stdout.puts "\e[33m Hit tool round limit (#{max_rounds}). Forcing final answer. Increase with: RailsConsoleAi.configure { |c| c.max_tool_rounds = 200 }\e[0m"
|
|
853
898
|
messages << { role: :user, content: "You've used all available tool rounds. Please provide your best answer now based on what you've learned so far." }
|
|
@@ -1110,16 +1155,14 @@ module RailsConsoleAi
|
|
|
1110
1155
|
.select { |m, _| m[:output_id] }
|
|
1111
1156
|
.map { |_, i| i }
|
|
1112
1157
|
|
|
1113
|
-
if output_indices.length <= RECENT_OUTPUTS_TO_KEEP
|
|
1114
|
-
return messages.map { |m| m.except(:output_id) }
|
|
1115
|
-
end
|
|
1158
|
+
return messages if output_indices.length <= RECENT_OUTPUTS_TO_KEEP
|
|
1116
1159
|
|
|
1117
1160
|
trim_indices = output_indices[0..-(RECENT_OUTPUTS_TO_KEEP + 1)]
|
|
1118
1161
|
messages.each_with_index.map do |msg, i|
|
|
1119
1162
|
if trim_indices.include?(i)
|
|
1120
1163
|
trim_message(msg)
|
|
1121
1164
|
else
|
|
1122
|
-
msg
|
|
1165
|
+
msg
|
|
1123
1166
|
end
|
|
1124
1167
|
end
|
|
1125
1168
|
end
|
|
@@ -1135,12 +1178,52 @@ module RailsConsoleAi
|
|
|
1135
1178
|
block
|
|
1136
1179
|
end
|
|
1137
1180
|
end
|
|
1138
|
-
|
|
1181
|
+
msg.merge(content: trimmed_content)
|
|
1139
1182
|
elsif msg[:role].to_s == 'tool'
|
|
1140
|
-
msg.
|
|
1183
|
+
msg.merge(content: ref)
|
|
1141
1184
|
else
|
|
1142
1185
|
first_line = msg[:content].to_s.lines.first&.strip || msg[:content]
|
|
1143
|
-
|
|
1186
|
+
msg.merge(content: "#{first_line}\n#{ref}")
|
|
1187
|
+
end
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1190
|
+
def expand_outputs_in_place(messages, ids)
|
|
1191
|
+
expanded = []
|
|
1192
|
+
messages.each do |msg|
|
|
1193
|
+
next unless msg[:output_id] && ids.include?(msg[:output_id])
|
|
1194
|
+
full_output = @executor.recall_output(msg[:output_id])
|
|
1195
|
+
next unless full_output
|
|
1196
|
+
# Save original content so re_truncate_expanded can restore it
|
|
1197
|
+
msg[:pre_expand_content] = msg[:content]
|
|
1198
|
+
# Replace content with full output (handle Anthropic, OpenAI, and user message formats)
|
|
1199
|
+
if msg[:content].is_a?(Array)
|
|
1200
|
+
msg[:content] = msg[:content].map do |block|
|
|
1201
|
+
if block.is_a?(Hash) && block['type'] == 'tool_result'
|
|
1202
|
+
block.merge('content' => full_output)
|
|
1203
|
+
else
|
|
1204
|
+
block
|
|
1205
|
+
end
|
|
1206
|
+
end
|
|
1207
|
+
elsif msg[:role].to_s == 'tool'
|
|
1208
|
+
msg[:content] = full_output
|
|
1209
|
+
else
|
|
1210
|
+
# User messages (e.g., direct execution) — preserve first line, replace rest
|
|
1211
|
+
first_line = msg[:content].to_s.lines.first&.chomp || ''
|
|
1212
|
+
msg[:content] = "#{first_line}\n#{full_output}"
|
|
1213
|
+
end
|
|
1214
|
+
msg[:expanded] = true
|
|
1215
|
+
expanded << msg[:output_id]
|
|
1216
|
+
end
|
|
1217
|
+
expanded
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1220
|
+
# Restore messages that were temporarily expanded back to their original
|
|
1221
|
+
# (preview/truncated) content. Called after the LLM has seen the expanded
|
|
1222
|
+
# content and responded.
|
|
1223
|
+
def re_truncate_expanded(messages)
|
|
1224
|
+
messages.each do |msg|
|
|
1225
|
+
next unless msg.delete(:expanded)
|
|
1226
|
+
msg[:content] = msg.delete(:pre_expand_content)
|
|
1144
1227
|
end
|
|
1145
1228
|
end
|
|
1146
1229
|
|
|
@@ -121,6 +121,8 @@ module RailsConsoleAi
|
|
|
121
121
|
else
|
|
122
122
|
[{ text: msg[:content].to_s }]
|
|
123
123
|
end
|
|
124
|
+
# Bedrock rejects empty text blocks in content arrays
|
|
125
|
+
content.reject! { |block| block.is_a?(Hash) && block.key?(:text) && !block.key?(:tool_use) && !block.key?(:tool_result) && block[:text].to_s.empty? }
|
|
124
126
|
{ role: msg[:role].to_s, content: content }
|
|
125
127
|
end
|
|
126
128
|
|
|
@@ -186,7 +186,7 @@ module RailsConsoleAi
|
|
|
186
186
|
if @executor
|
|
187
187
|
register(
|
|
188
188
|
name: 'recall_output',
|
|
189
|
-
description: 'Retrieve a previous code execution output that was omitted
|
|
189
|
+
description: 'Retrieve a previous code execution output that was omitted or truncated. The output will be expanded in place in the conversation. Use the output id shown in the "[Output omitted]" or "[Output truncated]" placeholder.',
|
|
190
190
|
parameters: {
|
|
191
191
|
'type' => 'object',
|
|
192
192
|
'properties' => {
|
|
@@ -199,6 +199,19 @@ module RailsConsoleAi
|
|
|
199
199
|
result || "No output found with id #{args['id']}"
|
|
200
200
|
}
|
|
201
201
|
)
|
|
202
|
+
|
|
203
|
+
register(
|
|
204
|
+
name: 'recall_outputs',
|
|
205
|
+
description: 'Retrieve multiple previous code execution outputs that were omitted from the conversation. Use the output ids shown in "[Output omitted]" or "[Output truncated]" placeholders.',
|
|
206
|
+
parameters: {
|
|
207
|
+
'type' => 'object',
|
|
208
|
+
'properties' => {
|
|
209
|
+
'ids' => { 'type' => 'array', 'items' => { 'type' => 'integer' }, 'description' => 'The output ids to retrieve' }
|
|
210
|
+
},
|
|
211
|
+
'required' => ['ids']
|
|
212
|
+
},
|
|
213
|
+
handler: ->(args) { "recall_outputs handled by conversation engine" }
|
|
214
|
+
)
|
|
202
215
|
end
|
|
203
216
|
|
|
204
217
|
unless @mode == :init
|