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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/Gemfile +2 -2
  4. data/Gemfile.lock +27 -17
  5. data/examples/agent_with_multiple_prompts.rb +27 -0
  6. data/examples/demo/Gemfile.lock +33 -1
  7. data/examples/plugin-gem-example/Gemfile.lock +5 -1
  8. data/examples/simple_pi_agent.rb +18 -0
  9. data/lib/roast/cog/output.rb +2 -1
  10. data/lib/roast/cog/registry.rb +3 -3
  11. data/lib/roast/cogs/agent/config.rb +1 -1
  12. data/lib/roast/cogs/agent/input.rb +20 -22
  13. data/lib/roast/cogs/agent/providers/claude/claude_invocation.rb +13 -5
  14. data/lib/roast/cogs/agent/providers/claude.rb +16 -3
  15. data/lib/roast/cogs/agent/providers/pi/messages/tool_call_message.rb +60 -0
  16. data/lib/roast/cogs/agent/providers/pi/messages/tool_result_message.rb +57 -0
  17. data/lib/roast/cogs/agent/providers/pi/pi_invocation.rb +352 -0
  18. data/lib/roast/cogs/agent/providers/pi.rb +41 -0
  19. data/lib/roast/cogs/agent/stats.rb +29 -0
  20. data/lib/roast/cogs/agent/usage.rb +22 -0
  21. data/lib/roast/cogs/agent.rb +2 -4
  22. data/lib/roast/version.rb +1 -1
  23. data/lib/roast.rb +1 -3
  24. data/roast-ai.gemspec +1 -0
  25. data/sorbet/rbi/gems/activesupport@8.0.2.rbi +549 -383
  26. data/sorbet/rbi/gems/addressable@2.8.7.rbi +46 -44
  27. data/sorbet/rbi/gems/ast@2.4.3.rbi +7 -6
  28. data/sorbet/rbi/gems/async@2.34.0.rbi +21 -3
  29. data/sorbet/rbi/gems/benchmark@0.4.1.rbi +7 -7
  30. data/sorbet/rbi/gems/bigdecimal@3.2.2.rbi +198 -1
  31. data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +405 -328
  32. data/sorbet/rbi/gems/console@1.34.2.rbi +2 -2
  33. data/sorbet/rbi/gems/docile@1.4.1.rbi +30 -30
  34. data/sorbet/rbi/gems/drb@2.2.3.rbi +25 -25
  35. data/sorbet/rbi/gems/erubi@1.13.1.rbi +2 -0
  36. data/sorbet/rbi/gems/faraday-net_http@3.4.2.rbi +2 -77
  37. data/sorbet/rbi/gems/faraday-retry@2.3.2.rbi +2 -57
  38. data/sorbet/rbi/gems/faraday@2.14.1.rbi +382 -75
  39. data/sorbet/rbi/gems/guard-compat@1.2.1.rbi +1 -110
  40. data/sorbet/rbi/gems/guard-minitest@2.4.6.rbi +0 -139
  41. data/sorbet/rbi/gems/guard@2.19.1.rbi +38 -38
  42. data/sorbet/rbi/gems/hashdiff@1.2.0.rbi +3 -3
  43. data/sorbet/rbi/gems/i18n@1.14.7.rbi +53 -29
  44. data/sorbet/rbi/gems/io-event@1.14.0.rbi +67 -10
  45. data/sorbet/rbi/gems/json@2.18.1.rbi +227 -5
  46. data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +83 -0
  47. data/sorbet/rbi/gems/listen@3.9.0.rbi +7 -7
  48. data/sorbet/rbi/gems/logger@1.7.0.rbi +3 -3
  49. data/sorbet/rbi/gems/lumberjack@1.2.10.rbi +21 -21
  50. data/sorbet/rbi/gems/marcel@1.1.0.rbi +1 -1
  51. data/sorbet/rbi/gems/minitest-rg@5.3.0.rbi +0 -96
  52. data/sorbet/rbi/gems/minitest@5.25.5.rbi +1 -16
  53. data/sorbet/rbi/gems/net-http@0.9.1.rbi +27 -19
  54. data/sorbet/rbi/gems/netrc@0.11.0.rbi +18 -0
  55. data/sorbet/rbi/gems/notiffany@0.1.3.rbi +20 -20
  56. data/sorbet/rbi/gems/ostruct@0.6.2.rbi +149 -15
  57. data/sorbet/rbi/gems/parser@3.3.8.0.rbi +141 -139
  58. data/sorbet/rbi/gems/prism@1.4.0.rbi +922 -864
  59. data/sorbet/rbi/gems/public_suffix@6.0.2.rbi +56 -35
  60. data/sorbet/rbi/gems/racc@1.8.1.rbi +10 -2
  61. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +12 -12
  62. data/sorbet/rbi/gems/rake@13.3.0.rbi +219 -318
  63. data/sorbet/rbi/gems/{rbi@0.3.6.rbi → rbi@0.3.9.rbi} +612 -2267
  64. data/sorbet/rbi/gems/{rbs@3.9.4.rbi → rbs@4.0.0.dev.5.rbi} +2013 -680
  65. data/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +151 -113
  66. data/sorbet/rbi/gems/require-hooks@0.2.3.rbi +110 -0
  67. data/sorbet/rbi/gems/rexml@3.4.2.rbi +24 -51
  68. data/sorbet/rbi/gems/rubocop-ast@1.45.1.rbi +506 -815
  69. data/sorbet/rbi/gems/rubocop-sorbet@0.10.5.rbi +16 -16
  70. data/sorbet/rbi/gems/rubocop@1.77.0.rbi +2692 -2327
  71. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +8 -8
  72. data/sorbet/rbi/gems/ruby_llm@1.8.2.rbi +38 -23
  73. data/sorbet/rbi/gems/securerandom@0.4.1.rbi +1 -1
  74. data/sorbet/rbi/gems/simplecov-html@0.13.2.rbi +2 -131
  75. data/sorbet/rbi/gems/simplecov@0.22.0.rbi +28 -127
  76. data/sorbet/rbi/gems/{spoom@1.6.3.rbi → spoom@1.7.11.rbi} +1139 -2246
  77. data/sorbet/rbi/gems/sqlite3@2.9.0.rbi +91 -1
  78. data/sorbet/rbi/gems/{tapioca@0.16.11.rbi → tapioca@0.17.10.rbi} +721 -835
  79. data/sorbet/rbi/gems/thor@1.4.0.rbi +53 -53
  80. data/sorbet/rbi/gems/tsort@0.2.0.rbi +393 -0
  81. data/sorbet/rbi/gems/type_toolkit@0.0.5.rbi +49 -0
  82. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +144 -143
  83. data/sorbet/rbi/gems/uri@1.1.1.rbi +7 -7
  84. data/sorbet/rbi/gems/vcr@6.3.1.rbi +53 -36
  85. data/sorbet/rbi/gems/webmock@3.25.1.rbi +38 -13
  86. data/sorbet/rbi/gems/zeitwerk@2.7.3.rbi +39 -272
  87. data/sorbet/rbi/shims/lib/roast/execution_context.rbi +3 -3
  88. metadata +29 -11
  89. data/docs/AGENT_STEPS.md +0 -288
  90. data/docs/INSTRUMENTATION.md +0 -243
  91. data/docs/ITERATION_SYNTAX.md +0 -147
  92. data/docs/VALIDATION.md +0 -178
  93. data/lib/roast/nil_assertions.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 309884973e075e4d94e5eb7ea3b3192ebe5cbcf5d08a60312c1054f9567d4eb9
4
- data.tar.gz: b79c03de093e577e77b480e7c67ac5dd510572eeb51887ea9147a03474a6f76a
3
+ metadata.gz: 493182d7cf3f626050e7e580402cda301cccf7da038072fec060e8e142a067a8
4
+ data.tar.gz: b1a789f8383a9644f0a7d91c7485b14def95574ff5a5e59e7be7ca17a1bf3ff5
5
5
  SHA512:
6
- metadata.gz: 78d6f38126192dd69853a1bfe8bfe15c5f8ee6ee34f4cf998af534ff215c7a774e0af250409de94027ae36ed0b159a624ad6c4f1c837c40dfc24b72a8b6fe1cd
7
- data.tar.gz: f36f8a9ad38f0c4e8d0ee15b88c212a59d831613024d9825f362fceba8619d1ad27289142e5f47c53300ac6036753733b7c4c5d3cc27781efbc21f54c63b69de
6
+ metadata.gz: 30044c47daf5c6c6856b5d947c2740a0df631f59079332cb15d01c02db18aec6a943b193e3b54718162379d186f9e33ca08b163fe1b2d98d13c2da0c5aa6f358
7
+ data.tar.gz: 1546e8c8128a4bf80a55faa80b07872bb12faaac9b21b61f75f62a4024f50b033441be0a8f9883b29af15b8d0951121e09c80a68c8c3a4fc69a8df7a298f5704
data/.rubocop.yml CHANGED
@@ -2,6 +2,8 @@ inherit_from: .rubocop_todo.yml
2
2
 
3
3
  plugins:
4
4
  - rubocop-sorbet
5
+ - type_toolkit:
6
+ require_path: rubocop-type_toolkit
5
7
 
6
8
  inherit_gem:
7
9
  rubocop-shopify: rubocop.yml
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", "~> 0.5.12414", require: false
20
+ gem "sorbet", ">= 0.6.12698", require: false
21
21
  gem "sqlite3", require: false
22
- gem "tapioca", "~> 0.16.11", require: false
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.2)
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.6)
130
+ rbi (0.3.9)
130
131
  prism (~> 1.0)
131
132
  rbs (>= 3.4.4)
132
- rbs (3.9.4)
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.5.12414)
175
- sorbet-static (= 0.5.12414)
176
- sorbet-runtime (0.5.12414)
177
- sorbet-static (0.5.12414-universal-darwin)
178
- sorbet-static (0.5.12414-x86_64-linux)
179
- sorbet-static-and-runtime (0.5.12414)
180
- sorbet (= 0.5.12414)
181
- sorbet-runtime (= 0.5.12414)
182
- spoom (1.6.3)
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.16.11)
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 (~> 0.2)
201
+ rbi (>= 0.3.7)
202
+ require-hooks (>= 0.2.2)
197
203
  sorbet-static-and-runtime (>= 0.5.11087)
198
- spoom (>= 1.2.0)
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 (~> 0.5.12414)
246
+ sorbet (>= 0.6.12698)
237
247
  sqlite3
238
- tapioca (~> 0.16.11)
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
@@ -1,11 +1,12 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- roast-ai (1.0.2)
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.2)
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
@@ -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
@@ -51,13 +51,13 @@ module Roast
51
51
  #
52
52
  #: (singleton(Roast::Cog)) -> void
53
53
  def use(cog_class)
54
- reg = create_registration(cog_class)
55
- cogs[reg.first] = reg.second
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)) -> Array(Symbol, singleton(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 prompt to send to the agent for processing
12
+ # The prompts to send to the agent for processing
13
13
  #
14
- #: String?
15
- attr_accessor :prompt
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
- @prompt = nil #: String?
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
- valid_prompt!
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
- if input_return_value.is_a?(String)
57
- self.prompt ||= input_return_value
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
- # Get the validated prompt value
62
- #
63
- # Returns the prompt if it is present, otherwise raises an `InvalidInputError`.
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, Agent::Input) -> void
57
- def initialize(config, input)
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
- command.push("--fork-session", "--resume", @session) if @session.present?
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
- invocation = ClaudeInvocation.new(@config, input)
22
- invocation.run!
23
- Output.new(invocation.result)
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