roast-ai 1.0.2 → 1.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +27 -17
- data/examples/agent_with_multiple_prompts.rb +27 -0
- data/examples/demo/Gemfile.lock +33 -1
- data/examples/plugin-gem-example/Gemfile.lock +5 -1
- data/examples/simple_pi_agent.rb +18 -0
- data/lib/roast/cog/output.rb +2 -1
- data/lib/roast/cog/registry.rb +3 -3
- data/lib/roast/cogs/agent/config.rb +1 -1
- data/lib/roast/cogs/agent/input.rb +20 -22
- data/lib/roast/cogs/agent/providers/claude/claude_invocation.rb +13 -5
- data/lib/roast/cogs/agent/providers/claude.rb +16 -3
- data/lib/roast/cogs/agent/providers/pi/messages/tool_call_message.rb +60 -0
- data/lib/roast/cogs/agent/providers/pi/messages/tool_result_message.rb +57 -0
- data/lib/roast/cogs/agent/providers/pi/pi_invocation.rb +352 -0
- data/lib/roast/cogs/agent/providers/pi.rb +41 -0
- data/lib/roast/cogs/agent/stats.rb +29 -0
- data/lib/roast/cogs/agent/usage.rb +22 -0
- data/lib/roast/cogs/agent.rb +2 -4
- data/lib/roast/version.rb +1 -1
- data/lib/roast.rb +1 -3
- data/roast-ai.gemspec +1 -0
- data/sorbet/rbi/gems/activesupport@8.0.2.rbi +549 -383
- data/sorbet/rbi/gems/addressable@2.8.7.rbi +46 -44
- data/sorbet/rbi/gems/ast@2.4.3.rbi +7 -6
- data/sorbet/rbi/gems/async@2.34.0.rbi +21 -3
- data/sorbet/rbi/gems/benchmark@0.4.1.rbi +7 -7
- data/sorbet/rbi/gems/bigdecimal@3.2.2.rbi +198 -1
- data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +405 -328
- data/sorbet/rbi/gems/console@1.34.2.rbi +2 -2
- data/sorbet/rbi/gems/docile@1.4.1.rbi +30 -30
- data/sorbet/rbi/gems/drb@2.2.3.rbi +25 -25
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +2 -0
- data/sorbet/rbi/gems/faraday-net_http@3.4.2.rbi +2 -77
- data/sorbet/rbi/gems/faraday-retry@2.3.2.rbi +2 -57
- data/sorbet/rbi/gems/faraday@2.14.1.rbi +382 -75
- data/sorbet/rbi/gems/guard-compat@1.2.1.rbi +1 -110
- data/sorbet/rbi/gems/guard-minitest@2.4.6.rbi +0 -139
- data/sorbet/rbi/gems/guard@2.19.1.rbi +38 -38
- data/sorbet/rbi/gems/hashdiff@1.2.0.rbi +3 -3
- data/sorbet/rbi/gems/i18n@1.14.7.rbi +53 -29
- data/sorbet/rbi/gems/io-event@1.14.0.rbi +67 -10
- data/sorbet/rbi/gems/json@2.18.1.rbi +227 -5
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +83 -0
- data/sorbet/rbi/gems/listen@3.9.0.rbi +7 -7
- data/sorbet/rbi/gems/logger@1.7.0.rbi +3 -3
- data/sorbet/rbi/gems/lumberjack@1.2.10.rbi +21 -21
- data/sorbet/rbi/gems/marcel@1.1.0.rbi +1 -1
- data/sorbet/rbi/gems/minitest-rg@5.3.0.rbi +0 -96
- data/sorbet/rbi/gems/minitest@5.25.5.rbi +1 -16
- data/sorbet/rbi/gems/net-http@0.9.1.rbi +27 -19
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +18 -0
- data/sorbet/rbi/gems/notiffany@0.1.3.rbi +20 -20
- data/sorbet/rbi/gems/ostruct@0.6.2.rbi +149 -15
- data/sorbet/rbi/gems/parser@3.3.8.0.rbi +141 -139
- data/sorbet/rbi/gems/prism@1.4.0.rbi +922 -864
- data/sorbet/rbi/gems/public_suffix@6.0.2.rbi +56 -35
- data/sorbet/rbi/gems/racc@1.8.1.rbi +10 -2
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +12 -12
- data/sorbet/rbi/gems/rake@13.3.0.rbi +219 -318
- data/sorbet/rbi/gems/{rbi@0.3.6.rbi → rbi@0.3.9.rbi} +612 -2267
- data/sorbet/rbi/gems/{rbs@3.9.4.rbi → rbs@4.0.0.dev.5.rbi} +2013 -680
- data/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +151 -113
- data/sorbet/rbi/gems/require-hooks@0.2.3.rbi +110 -0
- data/sorbet/rbi/gems/rexml@3.4.2.rbi +24 -51
- data/sorbet/rbi/gems/rubocop-ast@1.45.1.rbi +506 -815
- data/sorbet/rbi/gems/rubocop-sorbet@0.10.5.rbi +16 -16
- data/sorbet/rbi/gems/rubocop@1.77.0.rbi +2692 -2327
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +8 -8
- data/sorbet/rbi/gems/ruby_llm@1.8.2.rbi +38 -23
- data/sorbet/rbi/gems/securerandom@0.4.1.rbi +1 -1
- data/sorbet/rbi/gems/simplecov-html@0.13.2.rbi +2 -131
- data/sorbet/rbi/gems/simplecov@0.22.0.rbi +28 -127
- data/sorbet/rbi/gems/{spoom@1.6.3.rbi → spoom@1.7.11.rbi} +1139 -2246
- data/sorbet/rbi/gems/sqlite3@2.9.0.rbi +91 -1
- data/sorbet/rbi/gems/{tapioca@0.16.11.rbi → tapioca@0.17.10.rbi} +721 -835
- data/sorbet/rbi/gems/thor@1.4.0.rbi +53 -53
- data/sorbet/rbi/gems/tsort@0.2.0.rbi +393 -0
- data/sorbet/rbi/gems/type_toolkit@0.0.5.rbi +49 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +144 -143
- data/sorbet/rbi/gems/uri@1.1.1.rbi +7 -7
- data/sorbet/rbi/gems/vcr@6.3.1.rbi +53 -36
- data/sorbet/rbi/gems/webmock@3.25.1.rbi +38 -13
- data/sorbet/rbi/gems/zeitwerk@2.7.3.rbi +39 -272
- data/sorbet/rbi/shims/lib/roast/execution_context.rbi +3 -3
- metadata +29 -11
- data/docs/AGENT_STEPS.md +0 -288
- data/docs/INSTRUMENTATION.md +0 -243
- data/docs/ITERATION_SYNTAX.md +0 -147
- data/docs/VALIDATION.md +0 -178
- data/lib/roast/nil_assertions.rb +0 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 493182d7cf3f626050e7e580402cda301cccf7da038072fec060e8e142a067a8
|
|
4
|
+
data.tar.gz: b1a789f8383a9644f0a7d91c7485b14def95574ff5a5e59e7be7ca17a1bf3ff5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30044c47daf5c6c6856b5d947c2740a0df631f59079332cb15d01c02db18aec6a943b193e3b54718162379d186f9e33ca08b163fe1b2d98d13c2da0c5aa6f358
|
|
7
|
+
data.tar.gz: 1546e8c8128a4bf80a55faa80b07872bb12faaac9b21b61f75f62a4024f50b033441be0a8f9883b29af15b8d0951121e09c80a68c8c3a4fc69a8df7a298f5704
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
|
@@ -17,9 +17,9 @@ group :development, :test do
|
|
|
17
17
|
gem "rubocop-shopify", require: false
|
|
18
18
|
gem "rubocop-sorbet", require: false
|
|
19
19
|
gem "simplecov", require: false
|
|
20
|
-
gem "sorbet", "
|
|
20
|
+
gem "sorbet", ">= 0.6.12698", require: false
|
|
21
21
|
gem "sqlite3", require: false
|
|
22
|
-
gem "tapioca", "
|
|
22
|
+
gem "tapioca", ">= 0.17.8", require: false
|
|
23
23
|
gem "vcr", require: false
|
|
24
24
|
gem "webmock", require: false
|
|
25
25
|
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
roast-ai (1.0
|
|
4
|
+
roast-ai (1.1.0)
|
|
5
5
|
activesupport (~> 8.0)
|
|
6
6
|
async (>= 2.34)
|
|
7
7
|
rainbow (>= 3.0.0)
|
|
8
8
|
ruby_llm (>= 1.8)
|
|
9
|
+
type_toolkit (>= 0.0.5)
|
|
9
10
|
zeitwerk (>= 2.6)
|
|
10
11
|
|
|
11
12
|
GEM
|
|
@@ -126,12 +127,15 @@ GEM
|
|
|
126
127
|
rb-fsevent (0.11.2)
|
|
127
128
|
rb-inotify (0.11.1)
|
|
128
129
|
ffi (~> 1.0)
|
|
129
|
-
rbi (0.3.
|
|
130
|
+
rbi (0.3.9)
|
|
130
131
|
prism (~> 1.0)
|
|
131
132
|
rbs (>= 3.4.4)
|
|
132
|
-
rbs (
|
|
133
|
+
rbs (4.0.0.dev.5)
|
|
133
134
|
logger
|
|
135
|
+
prism (>= 1.3.0)
|
|
136
|
+
tsort
|
|
134
137
|
regexp_parser (2.10.0)
|
|
138
|
+
require-hooks (0.2.3)
|
|
135
139
|
rexml (3.4.2)
|
|
136
140
|
rubocop (1.77.0)
|
|
137
141
|
json (~> 2.3)
|
|
@@ -171,35 +175,41 @@ GEM
|
|
|
171
175
|
simplecov_json_formatter (~> 0.1)
|
|
172
176
|
simplecov-html (0.13.2)
|
|
173
177
|
simplecov_json_formatter (0.1.4)
|
|
174
|
-
sorbet (0.
|
|
175
|
-
sorbet-static (= 0.
|
|
176
|
-
sorbet-runtime (0.
|
|
177
|
-
sorbet-static (0.
|
|
178
|
-
sorbet-static (0.
|
|
179
|
-
sorbet-static-and-runtime (0.
|
|
180
|
-
sorbet (= 0.
|
|
181
|
-
sorbet-runtime (= 0.
|
|
182
|
-
spoom (1.
|
|
178
|
+
sorbet (0.6.12984)
|
|
179
|
+
sorbet-static (= 0.6.12984)
|
|
180
|
+
sorbet-runtime (0.6.12984)
|
|
181
|
+
sorbet-static (0.6.12984-universal-darwin)
|
|
182
|
+
sorbet-static (0.6.12984-x86_64-linux)
|
|
183
|
+
sorbet-static-and-runtime (0.6.12984)
|
|
184
|
+
sorbet (= 0.6.12984)
|
|
185
|
+
sorbet-runtime (= 0.6.12984)
|
|
186
|
+
spoom (1.7.11)
|
|
183
187
|
erubi (>= 1.10.0)
|
|
184
188
|
prism (>= 0.28.0)
|
|
185
189
|
rbi (>= 0.3.3)
|
|
190
|
+
rbs (>= 4.0.0.dev.4)
|
|
186
191
|
rexml (>= 3.2.6)
|
|
187
192
|
sorbet-static-and-runtime (>= 0.5.10187)
|
|
188
193
|
thor (>= 0.19.2)
|
|
189
194
|
sqlite3 (2.9.0-arm64-darwin)
|
|
190
195
|
sqlite3 (2.9.0-x86_64-linux-gnu)
|
|
191
|
-
tapioca (0.
|
|
196
|
+
tapioca (0.17.10)
|
|
192
197
|
benchmark
|
|
193
198
|
bundler (>= 2.2.25)
|
|
194
199
|
netrc (>= 0.11.0)
|
|
195
200
|
parallel (>= 1.21.0)
|
|
196
|
-
rbi (
|
|
201
|
+
rbi (>= 0.3.7)
|
|
202
|
+
require-hooks (>= 0.2.2)
|
|
197
203
|
sorbet-static-and-runtime (>= 0.5.11087)
|
|
198
|
-
spoom (>= 1.
|
|
204
|
+
spoom (>= 1.7.9)
|
|
199
205
|
thor (>= 1.2.0)
|
|
200
206
|
yard-sorbet
|
|
201
207
|
thor (1.4.0)
|
|
202
208
|
traces (0.18.2)
|
|
209
|
+
tsort (0.2.0)
|
|
210
|
+
type_toolkit (0.0.5)
|
|
211
|
+
lint_roller
|
|
212
|
+
rubocop (>= 1.72.0)
|
|
203
213
|
tzinfo (2.0.6)
|
|
204
214
|
concurrent-ruby (~> 1.0)
|
|
205
215
|
unicode-display_width (3.1.4)
|
|
@@ -233,9 +243,9 @@ DEPENDENCIES
|
|
|
233
243
|
rubocop-shopify
|
|
234
244
|
rubocop-sorbet
|
|
235
245
|
simplecov
|
|
236
|
-
sorbet (
|
|
246
|
+
sorbet (>= 0.6.12698)
|
|
237
247
|
sqlite3
|
|
238
|
-
tapioca (
|
|
248
|
+
tapioca (>= 0.17.8)
|
|
239
249
|
vcr
|
|
240
250
|
webmock
|
|
241
251
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
agent do
|
|
8
|
+
provider :claude
|
|
9
|
+
model "haiku"
|
|
10
|
+
show_prompt!
|
|
11
|
+
dump_raw_agent_messages_to "tmp/claude-messages2.log"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
execute do
|
|
16
|
+
agent(:multi_step) do
|
|
17
|
+
[
|
|
18
|
+
"What is 2+2?",
|
|
19
|
+
"Now multiply that by 3",
|
|
20
|
+
"Now subtract 5"
|
|
21
|
+
]
|
|
22
|
+
end
|
|
23
|
+
ruby do
|
|
24
|
+
answer = agent!(:multi_step).integer!
|
|
25
|
+
puts "((2 + 2) * 3) - 5 = #{answer}"
|
|
26
|
+
end
|
|
27
|
+
end
|
data/examples/demo/Gemfile.lock
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../..
|
|
3
3
|
specs:
|
|
4
|
-
roast-ai (1.0
|
|
4
|
+
roast-ai (1.1.0)
|
|
5
5
|
activesupport (~> 8.0)
|
|
6
6
|
async (>= 2.34)
|
|
7
7
|
rainbow (>= 3.0.0)
|
|
8
8
|
ruby_llm (>= 1.8)
|
|
9
|
+
type_toolkit (>= 0.0.5)
|
|
9
10
|
zeitwerk (>= 2.6)
|
|
10
11
|
|
|
11
12
|
PATH
|
|
@@ -30,6 +31,7 @@ GEM
|
|
|
30
31
|
securerandom (>= 0.3)
|
|
31
32
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
32
33
|
uri (>= 0.13.1)
|
|
34
|
+
ast (2.4.3)
|
|
33
35
|
async (2.34.0)
|
|
34
36
|
console (~> 1.29)
|
|
35
37
|
fiber-annotation
|
|
@@ -65,6 +67,8 @@ GEM
|
|
|
65
67
|
concurrent-ruby (~> 1.0)
|
|
66
68
|
io-event (1.14.0)
|
|
67
69
|
json (2.18.1)
|
|
70
|
+
language_server-protocol (3.17.0.5)
|
|
71
|
+
lint_roller (1.1.0)
|
|
68
72
|
logger (1.7.0)
|
|
69
73
|
marcel (1.1.0)
|
|
70
74
|
metrics (0.15.0)
|
|
@@ -72,7 +76,29 @@ GEM
|
|
|
72
76
|
multipart-post (2.4.1)
|
|
73
77
|
net-http (0.9.1)
|
|
74
78
|
uri (>= 0.11.1)
|
|
79
|
+
parallel (1.28.0)
|
|
80
|
+
parser (3.3.11.1)
|
|
81
|
+
ast (~> 2.4.1)
|
|
82
|
+
racc
|
|
83
|
+
prism (1.9.0)
|
|
84
|
+
racc (1.8.1)
|
|
75
85
|
rainbow (3.1.1)
|
|
86
|
+
regexp_parser (2.11.3)
|
|
87
|
+
rubocop (1.86.0)
|
|
88
|
+
json (~> 2.3)
|
|
89
|
+
language_server-protocol (~> 3.17.0.2)
|
|
90
|
+
lint_roller (~> 1.1.0)
|
|
91
|
+
parallel (~> 1.10)
|
|
92
|
+
parser (>= 3.3.0.2)
|
|
93
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
94
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
95
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
96
|
+
ruby-progressbar (~> 1.7)
|
|
97
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
98
|
+
rubocop-ast (1.49.1)
|
|
99
|
+
parser (>= 3.3.7.2)
|
|
100
|
+
prism (~> 1.7)
|
|
101
|
+
ruby-progressbar (1.13.0)
|
|
76
102
|
ruby_llm (1.8.2)
|
|
77
103
|
base64
|
|
78
104
|
event_stream_parser (~> 1)
|
|
@@ -84,8 +110,14 @@ GEM
|
|
|
84
110
|
zeitwerk (~> 2)
|
|
85
111
|
securerandom (0.4.1)
|
|
86
112
|
traces (0.18.2)
|
|
113
|
+
type_toolkit (0.0.5)
|
|
114
|
+
lint_roller
|
|
115
|
+
rubocop (>= 1.72.0)
|
|
87
116
|
tzinfo (2.0.6)
|
|
88
117
|
concurrent-ruby (~> 1.0)
|
|
118
|
+
unicode-display_width (3.2.0)
|
|
119
|
+
unicode-emoji (~> 4.1)
|
|
120
|
+
unicode-emoji (4.2.0)
|
|
89
121
|
uri (1.1.1)
|
|
90
122
|
zeitwerk (2.7.3)
|
|
91
123
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../..
|
|
3
3
|
specs:
|
|
4
|
-
roast-ai (1.0
|
|
4
|
+
roast-ai (1.1.0)
|
|
5
5
|
activesupport (~> 8.0)
|
|
6
6
|
async (>= 2.34)
|
|
7
7
|
rainbow (>= 3.0.0)
|
|
8
8
|
ruby_llm (>= 1.8)
|
|
9
|
+
type_toolkit (>= 0.0.5)
|
|
9
10
|
zeitwerk (>= 2.6)
|
|
10
11
|
|
|
11
12
|
PATH
|
|
@@ -133,6 +134,9 @@ GEM
|
|
|
133
134
|
stringio (3.1.7)
|
|
134
135
|
traces (0.18.2)
|
|
135
136
|
tsort (0.2.0)
|
|
137
|
+
type_toolkit (0.0.5)
|
|
138
|
+
lint_roller
|
|
139
|
+
rubocop (>= 1.72.0)
|
|
136
140
|
tzinfo (2.0.6)
|
|
137
141
|
concurrent-ruby (~> 1.0)
|
|
138
142
|
unicode-display_width (3.2.0)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
agent do
|
|
8
|
+
provider :pi
|
|
9
|
+
model "anthropic/claude-haiku-4-5-20251001"
|
|
10
|
+
append_system_prompt "Always respond in haiku form"
|
|
11
|
+
show_prompt!
|
|
12
|
+
dump_raw_agent_messages_to "tmp/pi-messages.log"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
execute do
|
|
17
|
+
agent { "What is the world's largest lake?" }
|
|
18
|
+
end
|
data/lib/roast/cog/output.rb
CHANGED
|
@@ -241,8 +241,9 @@ module Roast
|
|
|
241
241
|
lines.reverse.each do |line|
|
|
242
242
|
# Look for numbers with various separators, formats, and currency symbols (very permissive)
|
|
243
243
|
# Matches: 123, 1,234, 1_234, 1 234, 1.23, -1.23, 1.23e10, 1.23e-10
|
|
244
|
+
# Reversed so that the last number on the line is tried first (matching the bottom-up line order)
|
|
244
245
|
matches = line.scan(/-?[\d\s$¢£€¥.,_]+(?:[eE][+-]?\d+)?/) #: as Array[String]
|
|
245
|
-
candidates.concat(matches.map(&:strip))
|
|
246
|
+
candidates.concat(matches.map(&:strip).reverse)
|
|
246
247
|
end
|
|
247
248
|
|
|
248
249
|
candidates.compact.uniq
|
data/lib/roast/cog/registry.rb
CHANGED
|
@@ -51,13 +51,13 @@ module Roast
|
|
|
51
51
|
#
|
|
52
52
|
#: (singleton(Roast::Cog)) -> void
|
|
53
53
|
def use(cog_class)
|
|
54
|
-
|
|
55
|
-
cogs[
|
|
54
|
+
name, klass = create_registration(cog_class)
|
|
55
|
+
cogs[name] = klass
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
private
|
|
59
59
|
|
|
60
|
-
#: (singleton(Roast::Cog)) ->
|
|
60
|
+
#: (singleton(Roast::Cog)) -> [Symbol, singleton(Cog)]
|
|
61
61
|
def create_registration(cog_class)
|
|
62
62
|
cog_class_name = cog_class.name
|
|
63
63
|
raise CouldNotDeriveCogNameError if cog_class_name.nil?
|
|
@@ -5,7 +5,7 @@ module Roast
|
|
|
5
5
|
module Cogs
|
|
6
6
|
class Agent < Cog
|
|
7
7
|
class Config < Cog::Config
|
|
8
|
-
VALID_PROVIDERS = [:claude].freeze #: Array[Symbol]
|
|
8
|
+
VALID_PROVIDERS = [:claude, :pi].freeze #: Array[Symbol]
|
|
9
9
|
|
|
10
10
|
# Configure the cog to use a specified provider when invoking an agent
|
|
11
11
|
#
|
|
@@ -9,10 +9,14 @@ module Roast
|
|
|
9
9
|
# The agent cog requires a prompt that will be sent to the agent for processing.
|
|
10
10
|
# Optionally, a session identifier can be provided to maintain context across multiple invocations.
|
|
11
11
|
class Input < Cog::Input
|
|
12
|
-
# The
|
|
12
|
+
# The prompts to send to the agent for processing
|
|
13
13
|
#
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# When multiple prompts are specified, each subsequent prompt is passed to the agent as soon as it completes
|
|
15
|
+
# the previous one, in the same session throughout. This can be useful for helping to ensure the agent produces
|
|
16
|
+
# final outputs in the form you desire after performing a long and complex task.
|
|
17
|
+
#
|
|
18
|
+
#: Array[String]
|
|
19
|
+
attr_accessor :prompts
|
|
16
20
|
|
|
17
21
|
# Optional session identifier for maintaining conversation context
|
|
18
22
|
#
|
|
@@ -28,7 +32,7 @@ module Roast
|
|
|
28
32
|
#: () -> void
|
|
29
33
|
def initialize
|
|
30
34
|
super
|
|
31
|
-
@
|
|
35
|
+
@prompts = [] #: Array[String]
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
# Validate that the input has all required parameters
|
|
@@ -37,41 +41,35 @@ module Roast
|
|
|
37
41
|
#
|
|
38
42
|
# #### See Also
|
|
39
43
|
# - `coerce`
|
|
40
|
-
# - `valid_prompt!`
|
|
41
44
|
#
|
|
42
45
|
#: () -> void
|
|
43
46
|
def validate!
|
|
44
|
-
|
|
47
|
+
raise Cog::Input::InvalidInputError, "At least one prompt is required" unless prompts.present?
|
|
48
|
+
raise Cog::Input::InvalidInputError, "Blank prompts are not allowed" if prompts.any?(&:blank?)
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
# Coerce the input from the return value of the input block
|
|
48
52
|
#
|
|
49
53
|
# If the input block returns a String, it will be used as the prompt value.
|
|
54
|
+
# If the input block returns an Array of Strings, the first will be used as the prompt and the
|
|
55
|
+
# rest will be used as finalizers.
|
|
50
56
|
#
|
|
51
57
|
# #### See Also
|
|
52
58
|
# - `validate!`
|
|
53
59
|
#
|
|
54
60
|
#: (untyped) -> void
|
|
55
61
|
def coerce(input_return_value)
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
case input_return_value
|
|
63
|
+
when String
|
|
64
|
+
self.prompts = [input_return_value]
|
|
65
|
+
when Array
|
|
66
|
+
self.prompts = input_return_value.map(&:to_s)
|
|
58
67
|
end
|
|
59
68
|
end
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
# #### See Also
|
|
66
|
-
# - `prompt`
|
|
67
|
-
# - `validate!`
|
|
68
|
-
#
|
|
69
|
-
#: () -> String
|
|
70
|
-
def valid_prompt!
|
|
71
|
-
valid_prompt = @prompt
|
|
72
|
-
raise Cog::Input::InvalidInputError, "'prompt' is required" unless valid_prompt.present?
|
|
73
|
-
|
|
74
|
-
valid_prompt
|
|
70
|
+
#: (String) -> void
|
|
71
|
+
def prompt=(prompt)
|
|
72
|
+
@prompts = [prompt]
|
|
75
73
|
end
|
|
76
74
|
end
|
|
77
75
|
end
|
|
@@ -53,20 +53,23 @@ module Roast
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
#: (Agent::Config,
|
|
57
|
-
def initialize(config,
|
|
56
|
+
#: (Agent::Config, String, String?, ?fork_session: bool) -> void
|
|
57
|
+
def initialize(config, prompt, session, fork_session: true)
|
|
58
58
|
@base_command = config.valid_command #: (String | Array[String])?
|
|
59
59
|
@model = config.valid_model #: String?
|
|
60
60
|
@append_system_prompt = config.valid_append_system_prompt #: String?
|
|
61
61
|
@replace_system_prompt = config.valid_replace_system_prompt #: String?
|
|
62
62
|
@apply_permissions = config.apply_permissions? #: bool
|
|
63
63
|
@working_directory = config.valid_working_directory #: Pathname?
|
|
64
|
-
@prompt = input.valid_prompt! #: String
|
|
65
|
-
@session = input.session #: String?
|
|
66
64
|
@context = Context.new #: Context
|
|
67
65
|
@result = Result.new #: Result
|
|
68
66
|
@raw_dump_file = config.valid_dump_raw_agent_messages_to_path #: Pathname?
|
|
67
|
+
@show_prompt = config.show_prompt? #: bool
|
|
69
68
|
@show_progress = config.show_progress? #: bool
|
|
69
|
+
@show_response = config.show_response? #: bool
|
|
70
|
+
@prompt = prompt
|
|
71
|
+
@session = session
|
|
72
|
+
@fork_session = fork_session #: bool
|
|
70
73
|
end
|
|
71
74
|
|
|
72
75
|
#: () -> void
|
|
@@ -74,6 +77,7 @@ module Roast
|
|
|
74
77
|
raise ClaudeAlreadyStartedError if started?
|
|
75
78
|
|
|
76
79
|
@started = true
|
|
80
|
+
puts "[USER PROMPT] #{@prompt}" if @show_prompt
|
|
77
81
|
_stdout, stderr, status = CommandRunner.execute(
|
|
78
82
|
command_line,
|
|
79
83
|
working_directory: @working_directory,
|
|
@@ -83,6 +87,7 @@ module Roast
|
|
|
83
87
|
|
|
84
88
|
if status.success?
|
|
85
89
|
@completed = true
|
|
90
|
+
puts "[AGENT RESPONSE] #{@result.response}" if @show_response
|
|
86
91
|
else
|
|
87
92
|
@failed = true
|
|
88
93
|
@result.success = false
|
|
@@ -180,7 +185,10 @@ module Roast
|
|
|
180
185
|
command.push("--model", @model) if @model
|
|
181
186
|
command.push("--system-prompt", @replace_system_prompt) if @replace_system_prompt
|
|
182
187
|
command.push("--append-system-prompt", @append_system_prompt) if @append_system_prompt
|
|
183
|
-
|
|
188
|
+
if @session.present?
|
|
189
|
+
command.push("--fork-session") if @fork_session
|
|
190
|
+
command.push("--resume", @session)
|
|
191
|
+
end
|
|
184
192
|
command << "--dangerously-skip-permissions" unless @apply_permissions
|
|
185
193
|
command
|
|
186
194
|
end
|
|
@@ -18,9 +18,22 @@ module Roast
|
|
|
18
18
|
|
|
19
19
|
#: (Agent::Input) -> Agent::Output
|
|
20
20
|
def invoke(input)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
invocations = [] #: Array[ClaudeInvocation]
|
|
22
|
+
input.prompts.each do |prompt|
|
|
23
|
+
previous_session = invocations.last&.result&.session
|
|
24
|
+
invocation = ClaudeInvocation.new(
|
|
25
|
+
@config,
|
|
26
|
+
prompt,
|
|
27
|
+
previous_session || input.session,
|
|
28
|
+
fork_session: previous_session.nil?,
|
|
29
|
+
)
|
|
30
|
+
invocation.run!
|
|
31
|
+
invocations << invocation
|
|
32
|
+
break unless invocation.result.success
|
|
33
|
+
end
|
|
34
|
+
final_result = invocations.last.not_nil!.result
|
|
35
|
+
final_result.stats = invocations.filter_map { |i| i.result.stats }.reduce(:+) if invocations.size > 1
|
|
36
|
+
Output.new(final_result)
|
|
24
37
|
end
|
|
25
38
|
end
|
|
26
39
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Roast
|
|
5
|
+
module Cogs
|
|
6
|
+
class Agent < Cog
|
|
7
|
+
module Providers
|
|
8
|
+
class Pi < Provider
|
|
9
|
+
module Messages
|
|
10
|
+
# Represents a tool call made by the Pi agent
|
|
11
|
+
#
|
|
12
|
+
# In Pi's JSON protocol, tool calls appear as `toolcall_end` events within
|
|
13
|
+
# `message_update` messages, containing the tool name, id, and arguments.
|
|
14
|
+
class ToolCallMessage
|
|
15
|
+
#: String?
|
|
16
|
+
attr_reader :id
|
|
17
|
+
|
|
18
|
+
#: String?
|
|
19
|
+
attr_reader :name
|
|
20
|
+
|
|
21
|
+
#: Hash[Symbol, untyped]
|
|
22
|
+
attr_reader :arguments
|
|
23
|
+
|
|
24
|
+
#: (id: String?, name: String?, arguments: Hash[Symbol, untyped]) -> void
|
|
25
|
+
def initialize(id:, name:, arguments:)
|
|
26
|
+
@id = id
|
|
27
|
+
@name = name
|
|
28
|
+
@arguments = arguments
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#: () -> String?
|
|
32
|
+
def format
|
|
33
|
+
return unless name
|
|
34
|
+
|
|
35
|
+
case name.to_s.downcase
|
|
36
|
+
when "bash"
|
|
37
|
+
"BASH #{arguments[:command]}"
|
|
38
|
+
when "read"
|
|
39
|
+
"READ #{arguments[:path]}"
|
|
40
|
+
when "edit"
|
|
41
|
+
"EDIT #{arguments[:path]}"
|
|
42
|
+
when "write"
|
|
43
|
+
"WRITE #{arguments[:path]}"
|
|
44
|
+
when "grep"
|
|
45
|
+
"GREP #{arguments[:pattern]} #{arguments[:path]}"
|
|
46
|
+
when "find"
|
|
47
|
+
"FIND #{arguments[:pattern]} #{arguments[:path]}"
|
|
48
|
+
when "ls"
|
|
49
|
+
"LS #{arguments[:path]}"
|
|
50
|
+
else
|
|
51
|
+
"TOOL [#{name}] #{arguments.inspect}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Roast
|
|
5
|
+
module Cogs
|
|
6
|
+
class Agent < Cog
|
|
7
|
+
module Providers
|
|
8
|
+
class Pi < Provider
|
|
9
|
+
module Messages
|
|
10
|
+
# Represents a tool execution result from the Pi agent
|
|
11
|
+
#
|
|
12
|
+
# In Pi's JSON protocol, tool results appear as `tool_execution_end` events
|
|
13
|
+
# containing the result content and error status.
|
|
14
|
+
class ToolResultMessage
|
|
15
|
+
#: String?
|
|
16
|
+
attr_reader :tool_call_id
|
|
17
|
+
|
|
18
|
+
#: String?
|
|
19
|
+
attr_reader :tool_name
|
|
20
|
+
|
|
21
|
+
#: String?
|
|
22
|
+
attr_reader :content
|
|
23
|
+
|
|
24
|
+
#: bool
|
|
25
|
+
attr_reader :is_error
|
|
26
|
+
|
|
27
|
+
#: (tool_call_id: String?, tool_name: String?, content: String?, is_error: bool) -> void
|
|
28
|
+
def initialize(tool_call_id:, tool_name:, content:, is_error:)
|
|
29
|
+
@tool_call_id = tool_call_id
|
|
30
|
+
@tool_name = tool_name
|
|
31
|
+
@content = content
|
|
32
|
+
@is_error = is_error
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
#: (PiInvocation::Context) -> String?
|
|
36
|
+
def format(context)
|
|
37
|
+
tool_call = context.tool_call(tool_call_id)
|
|
38
|
+
name = tool_name || tool_call&.name || "unknown"
|
|
39
|
+
status = is_error ? "ERROR" : "OK"
|
|
40
|
+
|
|
41
|
+
# Truncate long tool results for progress display
|
|
42
|
+
c = content
|
|
43
|
+
display_content = if c && c.length > 200
|
|
44
|
+
"#{c[0..197]}..."
|
|
45
|
+
else
|
|
46
|
+
c
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
"#{name.upcase} #{status}#{display_content ? " #{display_content}" : ""}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|