ruby_agent 0.2.1 → 0.2.2
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/.rubocop.yml +58 -0
- data/Gemfile +12 -0
- data/README.md +58 -0
- data/Rakefile +34 -0
- data/lib/ruby_agent/version.rb +1 -1
- data/lib/ruby_agent.rb +43 -42
- metadata +12 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f16c4c8fdacecaee67784197c3a8fe8b0e00adc248115c3d8c8f873d954fafd
|
|
4
|
+
data.tar.gz: c94d916ea812575d2227a822df90cdb5dbdfe9ee41794af1be0375cc4aa4d9ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 46cf5e8405ce2661346832274d770c2ccdc19c55e53797553a7f9794f402fe654134b44908c2b46a0bf02bda13dc73d96b688a6f8a7de300bdd7ae85fd228700
|
|
7
|
+
data.tar.gz: 8fb706b0a8f0b88eb50fdea84081aa2ef8b3e581bdbf6de4f70d230c92ca99103f123a3781e8be7ff9ecd791977b42043a8eb04b05fffede4a1645914a074c45
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 3.4
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Exclude:
|
|
6
|
+
- 'vendor/**/*'
|
|
7
|
+
- 'tmp/**/*'
|
|
8
|
+
- 'bin/**/*'
|
|
9
|
+
|
|
10
|
+
Style/Documentation:
|
|
11
|
+
Enabled: false
|
|
12
|
+
|
|
13
|
+
Style/StringLiterals:
|
|
14
|
+
EnforcedStyle: double_quotes
|
|
15
|
+
|
|
16
|
+
Style/FrozenStringLiteralComment:
|
|
17
|
+
Enabled: false
|
|
18
|
+
|
|
19
|
+
Layout/LineLength:
|
|
20
|
+
Max: 125
|
|
21
|
+
|
|
22
|
+
Metrics/MethodLength:
|
|
23
|
+
Max: 50
|
|
24
|
+
|
|
25
|
+
Metrics/AbcSize:
|
|
26
|
+
Max: 40
|
|
27
|
+
|
|
28
|
+
Metrics/CyclomaticComplexity:
|
|
29
|
+
Max: 25
|
|
30
|
+
|
|
31
|
+
Metrics/PerceivedComplexity:
|
|
32
|
+
Max: 25
|
|
33
|
+
|
|
34
|
+
Metrics/BlockNesting:
|
|
35
|
+
Max: 5
|
|
36
|
+
|
|
37
|
+
Metrics/BlockLength:
|
|
38
|
+
Exclude:
|
|
39
|
+
- 'test/**/*'
|
|
40
|
+
- 'Rakefile'
|
|
41
|
+
|
|
42
|
+
Metrics/ClassLength:
|
|
43
|
+
Max: 350
|
|
44
|
+
|
|
45
|
+
Metrics/ParameterLists:
|
|
46
|
+
Max: 10
|
|
47
|
+
|
|
48
|
+
Lint/UnusedMethodArgument:
|
|
49
|
+
AllowUnusedKeywordArguments: true
|
|
50
|
+
|
|
51
|
+
Lint/EmptyBlock:
|
|
52
|
+
Enabled: false
|
|
53
|
+
|
|
54
|
+
Lint/SuppressedException:
|
|
55
|
+
AllowComments: true
|
|
56
|
+
|
|
57
|
+
Gemspec/RequiredRubyVersion:
|
|
58
|
+
Enabled: false
|
data/Gemfile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
|
|
3
|
+
gemspec
|
|
4
|
+
|
|
5
|
+
gem "minitest", "~> 5.0"
|
|
6
|
+
gem "minitest-reporters", "~> 1.6"
|
|
7
|
+
gem "rake", "~> 13.0"
|
|
8
|
+
|
|
9
|
+
group :development do
|
|
10
|
+
gem "bundler-audit", "~> 0.9", require: false
|
|
11
|
+
gem "rubocop", "~> 1.50", require: false
|
|
12
|
+
end
|
data/README.md
CHANGED
|
@@ -205,6 +205,64 @@ rescue RubyAgent::AgentError => e
|
|
|
205
205
|
end
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
## Development
|
|
209
|
+
|
|
210
|
+
### Contributing
|
|
211
|
+
|
|
212
|
+
1. Fork the repository: https://github.com/AllYourBot/ruby-agent
|
|
213
|
+
2. Create a feature branch: `git checkout -b my-new-feature`
|
|
214
|
+
3. Make your changes
|
|
215
|
+
4. Run the CI suite locally to ensure everything passes:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Run all CI tasks (linting + tests)
|
|
219
|
+
rake ci
|
|
220
|
+
|
|
221
|
+
# Or run tasks individually:
|
|
222
|
+
rake ci:test # Run test suite
|
|
223
|
+
rake ci:lint # Run RuboCop linter
|
|
224
|
+
rake ci:scan # Run security audit
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
5. Commit your changes: `git commit -am 'Add some feature'`
|
|
228
|
+
6. Push to your fork: `git push origin my-new-feature`
|
|
229
|
+
7. Create a Pull Request against the `main` branch
|
|
230
|
+
|
|
231
|
+
### Running Tests Locally
|
|
232
|
+
|
|
233
|
+
The test suite includes an integration test that runs Claude Code CLI locally:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# Run all tests
|
|
237
|
+
rake test
|
|
238
|
+
|
|
239
|
+
# Run a specific test
|
|
240
|
+
ruby test/ruby_agent_test.rb --name test_simple_agent_query
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Note**: Tests require Claude Code CLI to be installed on your machine (see Prerequisites section).
|
|
244
|
+
|
|
245
|
+
### Linting
|
|
246
|
+
|
|
247
|
+
We use RuboCop for code linting:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# Check for linting issues
|
|
251
|
+
rake ci:lint
|
|
252
|
+
|
|
253
|
+
# Auto-fix linting issues
|
|
254
|
+
bundle exec rubocop -a
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Publishing
|
|
258
|
+
|
|
259
|
+
Publishing to RubyGems happens automatically via GitHub Actions when code is merged to `main`. The version number is read from `lib/ruby_agent/version.rb`.
|
|
260
|
+
|
|
261
|
+
**Before merging a PR**, make sure to bump the version number appropriately:
|
|
262
|
+
- Patch version (0.2.1 → 0.2.2) for bug fixes
|
|
263
|
+
- Minor version (0.2.1 → 0.3.0) for new features
|
|
264
|
+
- Major version (0.2.1 → 1.0.0) for breaking changes
|
|
265
|
+
|
|
208
266
|
## License
|
|
209
267
|
|
|
210
268
|
MIT
|
data/Rakefile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
2
|
+
require "rake/testtask"
|
|
3
|
+
|
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
|
5
|
+
t.libs << "test"
|
|
6
|
+
t.libs << "lib"
|
|
7
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
task default: :test
|
|
11
|
+
|
|
12
|
+
namespace :ci do
|
|
13
|
+
desc "Run tests"
|
|
14
|
+
task :test do
|
|
15
|
+
sh "bundle exec rake test"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc "Run linter"
|
|
19
|
+
task :lint do
|
|
20
|
+
sh "bundle exec rubocop"
|
|
21
|
+
rescue StandardError
|
|
22
|
+
puts "Rubocop not configured yet, skipping..."
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Run security scan"
|
|
26
|
+
task :scan do
|
|
27
|
+
sh "bundle exec bundler-audit check --update"
|
|
28
|
+
rescue StandardError
|
|
29
|
+
puts "Bundler-audit not installed, skipping..."
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc "Run all CI tasks"
|
|
34
|
+
task ci: ["ci:lint", "ci:test"]
|
data/lib/ruby_agent/version.rb
CHANGED
data/lib/ruby_agent.rb
CHANGED
|
@@ -46,15 +46,15 @@ class RubyAgent
|
|
|
46
46
|
@pending_interrupt_request_id = nil
|
|
47
47
|
@deferred_exit = false
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
return if @session_key
|
|
50
|
+
|
|
51
|
+
inject_streaming_response({
|
|
52
|
+
type: "system",
|
|
53
|
+
subtype: "prompt",
|
|
54
|
+
system_prompt: @system_prompt,
|
|
55
|
+
timestamp: Time.now.utc.iso8601(6),
|
|
56
|
+
received_at: Time.now.utc.iso8601(6)
|
|
57
|
+
})
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def create_message_callback(name, &processor)
|
|
@@ -68,7 +68,7 @@ class RubyAgent
|
|
|
68
68
|
@on_message_callback = block
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
alias on_event on_message
|
|
72
72
|
|
|
73
73
|
def on_error(&block)
|
|
74
74
|
@on_error_callback = block
|
|
@@ -111,37 +111,37 @@ class RubyAgent
|
|
|
111
111
|
@wait_thr = nil
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
|
-
rescue => e
|
|
114
|
+
rescue StandardError => e
|
|
115
115
|
trigger_error(e)
|
|
116
116
|
raise
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def ask(text, sender_name: "User", additional: [])
|
|
120
120
|
formatted_text = if sender_name.downcase == "system"
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
<<~TEXT.strip
|
|
122
|
+
<system>
|
|
123
|
+
#{text}
|
|
124
|
+
</system>
|
|
125
|
+
TEXT
|
|
126
|
+
else
|
|
127
|
+
"#{sender_name}: #{text}"
|
|
128
|
+
end
|
|
129
129
|
formatted_text += extra_context(additional, sender_name:)
|
|
130
130
|
|
|
131
131
|
inject_streaming_response({
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
type: "user",
|
|
133
|
+
subtype: "new_message",
|
|
134
|
+
sender_name:,
|
|
135
|
+
text:,
|
|
136
|
+
formatted_text:,
|
|
137
|
+
timestamp: Time.now.utc.iso8601(6)
|
|
138
|
+
})
|
|
139
139
|
|
|
140
140
|
send_message(formatted_text)
|
|
141
141
|
end
|
|
142
142
|
|
|
143
143
|
def ask_after_interrupt(text, sender_name: "User", additional: [])
|
|
144
|
-
@pending_ask_after_interrupt = {text:, sender_name:, additional:}
|
|
144
|
+
@pending_ask_after_interrupt = { text:, sender_name:, additional: }
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
def send_system_message(text)
|
|
@@ -171,7 +171,7 @@ class RubyAgent
|
|
|
171
171
|
|
|
172
172
|
puts "→ stdout closed, waiting for process to exit..." if DEBUG
|
|
173
173
|
exit_status = @wait_thr.value
|
|
174
|
-
puts "→ Process exited with status: #{exit_status.success? ?
|
|
174
|
+
puts "→ Process exited with status: #{exit_status.success? ? 'success' : 'failure'}" if DEBUG
|
|
175
175
|
unless exit_status.success?
|
|
176
176
|
stderr_output = @stderr.read
|
|
177
177
|
raise ConnectionError, "Claude command failed: #{stderr_output}"
|
|
@@ -202,7 +202,9 @@ class RubyAgent
|
|
|
202
202
|
request_id = "req_#{@request_counter}_#{SecureRandom.hex(4)}"
|
|
203
203
|
|
|
204
204
|
@pending_interrupt_request_id = request_id if @pending_ask_after_interrupt
|
|
205
|
-
|
|
205
|
+
if DEBUG
|
|
206
|
+
puts "→ Sending interrupt with request_id: #{request_id}, pending_ask: #{@pending_ask_after_interrupt ? true : false}"
|
|
207
|
+
end
|
|
206
208
|
|
|
207
209
|
control_request = {
|
|
208
210
|
type: "control_request",
|
|
@@ -213,14 +215,14 @@ class RubyAgent
|
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
inject_streaming_response({
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
type: "control",
|
|
219
|
+
subtype: "interrupt",
|
|
220
|
+
timestamp: Time.now.utc.iso8601(6)
|
|
221
|
+
})
|
|
220
222
|
|
|
221
223
|
@stdin.puts JSON.generate(control_request)
|
|
222
224
|
@stdin.flush
|
|
223
|
-
rescue => e
|
|
225
|
+
rescue StandardError => e
|
|
224
226
|
warn "Failed to send interrupt signal: #{e.message}"
|
|
225
227
|
raise
|
|
226
228
|
end
|
|
@@ -239,7 +241,7 @@ class RubyAgent
|
|
|
239
241
|
begin
|
|
240
242
|
@stdin.close unless @stdin.closed?
|
|
241
243
|
puts "→ stdin closed" if DEBUG
|
|
242
|
-
rescue => e
|
|
244
|
+
rescue StandardError => e
|
|
243
245
|
warn "Error closing stdin during exit: #{e.message}"
|
|
244
246
|
end
|
|
245
247
|
end
|
|
@@ -267,7 +269,7 @@ class RubyAgent
|
|
|
267
269
|
|
|
268
270
|
def build_mcp_config(mcp_servers)
|
|
269
271
|
servers = mcp_servers.transform_keys { |k| k.to_s.gsub("_", "-") }
|
|
270
|
-
{mcpServers: servers}
|
|
272
|
+
{ mcpServers: servers }
|
|
271
273
|
end
|
|
272
274
|
|
|
273
275
|
def parse_system_prompt(template_content, context_vars)
|
|
@@ -285,9 +287,7 @@ class RubyAgent
|
|
|
285
287
|
binding_context = create_binding_context(**context_vars)
|
|
286
288
|
result = erb.result(binding_context)
|
|
287
289
|
|
|
288
|
-
if result.include?("<%=") || result.include?("%>")
|
|
289
|
-
raise ParseError, "There was an error parsing the system prompt."
|
|
290
|
-
end
|
|
290
|
+
raise ParseError, "There was an error parsing the system prompt." if result.include?("<%=") || result.include?("%>")
|
|
291
291
|
|
|
292
292
|
result
|
|
293
293
|
end
|
|
@@ -319,13 +319,13 @@ class RubyAgent
|
|
|
319
319
|
|
|
320
320
|
message_json = {
|
|
321
321
|
type: "user",
|
|
322
|
-
message: {role: "user", content: content},
|
|
322
|
+
message: { role: "user", content: content },
|
|
323
323
|
session_id: session_id
|
|
324
324
|
}.compact
|
|
325
325
|
|
|
326
326
|
@stdin.puts JSON.generate(message_json)
|
|
327
327
|
@stdin.flush
|
|
328
|
-
rescue => e
|
|
328
|
+
rescue StandardError => e
|
|
329
329
|
trigger_error(e)
|
|
330
330
|
raise
|
|
331
331
|
end
|
|
@@ -388,6 +388,7 @@ class RubyAgent
|
|
|
388
388
|
|
|
389
389
|
def check_nested_content_types(message, all_messages)
|
|
390
390
|
return unless message["message"].is_a?(Hash)
|
|
391
|
+
|
|
391
392
|
content = message.dig("message", "content")
|
|
392
393
|
return unless content.is_a?(Array)
|
|
393
394
|
|
|
@@ -406,7 +407,7 @@ class RubyAgent
|
|
|
406
407
|
end
|
|
407
408
|
|
|
408
409
|
def trigger_custom_message_callbacks(message, all_messages)
|
|
409
|
-
@custom_message_callbacks.
|
|
410
|
+
@custom_message_callbacks.each_value do |config|
|
|
410
411
|
processor = config[:processor]
|
|
411
412
|
callback = config[:callback]
|
|
412
413
|
|
metadata
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_agent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keith Schacht
|
|
8
|
+
- Matt Lindsey
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
12
|
dependencies: []
|
|
12
13
|
description: A framework for building AI agents in Ruby
|
|
13
14
|
email:
|
|
14
|
-
-
|
|
15
|
+
- krschacht@gmail.com
|
|
15
16
|
executables: []
|
|
16
17
|
extensions: []
|
|
17
18
|
extra_rdoc_files: []
|
|
18
19
|
files:
|
|
20
|
+
- ".rubocop.yml"
|
|
21
|
+
- Gemfile
|
|
19
22
|
- LICENSE
|
|
20
23
|
- README.md
|
|
24
|
+
- Rakefile
|
|
21
25
|
- lib/ruby_agent.rb
|
|
22
26
|
- lib/ruby_agent/version.rb
|
|
23
|
-
homepage: https://github.com/
|
|
27
|
+
homepage: https://github.com/AllYourBot/ruby-agent
|
|
24
28
|
licenses:
|
|
25
29
|
- MIT
|
|
26
30
|
metadata:
|
|
27
|
-
homepage_uri: https://github.com/
|
|
28
|
-
source_code_uri: https://github.com/
|
|
31
|
+
homepage_uri: https://github.com/AllYourBot/ruby-agent
|
|
32
|
+
source_code_uri: https://github.com/AllYourBot/ruby-agent
|
|
33
|
+
rubygems_mfa_required: 'true'
|
|
29
34
|
rdoc_options: []
|
|
30
35
|
require_paths:
|
|
31
36
|
- lib
|
|
@@ -33,14 +38,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
33
38
|
requirements:
|
|
34
39
|
- - ">="
|
|
35
40
|
- !ruby/object:Gem::Version
|
|
36
|
-
version: 2.
|
|
41
|
+
version: 3.2.0
|
|
37
42
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
43
|
requirements:
|
|
39
44
|
- - ">="
|
|
40
45
|
- !ruby/object:Gem::Version
|
|
41
46
|
version: '0'
|
|
42
47
|
requirements: []
|
|
43
|
-
rubygems_version: 3.6.
|
|
48
|
+
rubygems_version: 3.6.9
|
|
44
49
|
specification_version: 4
|
|
45
50
|
summary: Ruby agent framework
|
|
46
51
|
test_files: []
|