roast-ai 1.3.0 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e2c7eb637034ea1c7a97776161d901ddcce583600bb465fd1f403a4866203c4
4
- data.tar.gz: 194f9efd30453dbf123f16abab80bce99cf713dee8077858c10fa3e80c53d7cb
3
+ metadata.gz: a1acd0533e7f7ca21ccb4d8fd27f00bd1e838953130a5e0dd779e8be4a61c15e
4
+ data.tar.gz: cf9cb0909d17aefdf6dd066422251565a13d9abc152e202f9143080eec2b8fdd
5
5
  SHA512:
6
- metadata.gz: 92f4fc82409d7b59fed96a2ba31ac9c9c4dc9b9744a8645b934223c2fad8f09f5e960dcb8629963af619310516931391b90e0692162dbe42028553e311cb9eb9
7
- data.tar.gz: 5d12e5e669c9acc5ac3372f621c2627735c0edaf96bf2c674c56ebbee458b69d3c1fc3dea30073045541e06b01c6ece526bcd597de184e5ea6b84fca8a67f2cb
6
+ metadata.gz: df929fe8cd3a22bdbb5d22660a0ebec7461c67cb6d9880b13862f8bbc059ccc3e964fb34d142a631bbcf0e5a3c3ea88e5c19eb2727486d5fc82c2832f7f79355
7
+ data.tar.gz: 8245dca9407394314434006628279cb26a74dd9c72b8819cc21ba4c45728a790d91f4ec95e10ba999f6958e576981a493fdb8a3d419dc067873d6b40c0764d64
data/CONTRIBUTING.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # How to contribute
2
2
 
3
- > **📢 Code Freeze Notice**: This project is currently under a limited code freeze while we work on an alternative frontend (a DSL). We are still accepting **bug fixes**, but **new features** will not be merged until the DSL is released. Thank you for your understanding!
4
-
5
3
  ## Things we will merge
6
4
 
7
5
  * Bugfixes
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- roast-ai (1.3.0)
4
+ roast-ai (1.4.0)
5
5
  activesupport (~> 8.0)
6
6
  async (>= 2.34)
7
7
  rainbow (>= 3.0.0)
@@ -317,7 +317,7 @@ CHECKSUMS
317
317
  regexp_parser (2.10.0) sha256=cb6f0ddde88772cd64bff1dbbf68df66d376043fe2e66a9ef77fcb1b0c548c61
318
318
  require-hooks (0.2.3) sha256=224be5b4be0fd2a47cb73286c500da366704a54ec195b6627366380c950efac8
319
319
  rexml (3.4.2) sha256=1384268554a37af5da5279431ca3f2f37d46f09ffdd6c95e17cc84c83ea7c417
320
- roast-ai (1.3.0)
320
+ roast-ai (1.4.0)
321
321
  rubocop (1.77.0) sha256=1f360b4575ef7a124be27b0dfffa227a2b2d9420d22d4fd8bf179d702bcc88c0
322
322
  rubocop-ast (1.45.1) sha256=94042e49adc17f187ba037b33f941ba7398fede77cdf4bffafba95190a473a3e
323
323
  rubocop-shopify (2.17.1) sha256=03850eb1a9c4d1f9f0ac1d8d5aa51bb47a149e532cfb5e8d02ac6a90c8800a5f
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- roast-ai (1.3.0)
4
+ roast-ai (1.4.0)
5
5
  activesupport (~> 8.0)
6
6
  async (>= 2.34)
7
7
  rainbow (>= 3.0.0)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- roast-ai (1.3.0)
4
+ roast-ai (1.4.0)
5
5
  activesupport (~> 8.0)
6
6
  async (>= 2.34)
7
7
  rainbow (>= 3.0.0)
@@ -554,6 +554,49 @@ module Roast
554
554
  Pathname.new(@values[:dump_raw_agent_messages_to]) if @values[:dump_raw_agent_messages_to]
555
555
  end
556
556
 
557
+ # Configure the cog to append a record of each agent run to a file, as one JSON object per line
558
+ #
559
+ # Each record holds the run's start and finish times, the cog's name and its path in the workflow,
560
+ # the provider and model, the prompts and any appended or replacement system prompt, the final
561
+ # response, the session identifier, the execution statistics, and the run's status (`succeeded`,
562
+ # `failed`, or `timed_out`) with the error class and message if it did not succeed.
563
+ #
564
+ # Records include the full prompts and response, so they may contain sensitive text.
565
+ # Disabled by default.
566
+ #
567
+ # #### See Also
568
+ # - `no_record_agent_runs!`
569
+ #
570
+ #: (String) -> void
571
+ def record_agent_runs_to(filename)
572
+ @values[:record_agent_runs_to] = filename
573
+ end
574
+
575
+ # Configure the cog __not__ to record its agent runs
576
+ #
577
+ # This is the default behaviour.
578
+ #
579
+ # #### See Also
580
+ # - `record_agent_runs_to`
581
+ #
582
+ #: () -> void
583
+ def no_record_agent_runs!
584
+ @values[:record_agent_runs_to] = nil
585
+ end
586
+
587
+ # Get the validated, configured path to which agent run records should be appended
588
+ #
589
+ # Returns `nil` if agent runs should not be recorded.
590
+ #
591
+ # #### See Also
592
+ # - `record_agent_runs_to`
593
+ # - `no_record_agent_runs!`
594
+ #
595
+ #: () -> Pathname?
596
+ def valid_record_agent_runs_to_path
597
+ Pathname.new(@values[:record_agent_runs_to]) if @values[:record_agent_runs_to].present?
598
+ end
599
+
557
600
  alias_method(:skip_permissions!, :no_apply_permissions!)
558
601
  alias_method(:no_skip_permissions!, :apply_permissions!)
559
602
  alias_method(:quiet!, :no_display!)
@@ -14,8 +14,10 @@ module Roast
14
14
  class << self
15
15
  #: (String, ?raw_dump_file: Pathname?) -> Message
16
16
  def from_json(json, raw_dump_file: nil)
17
- raw_dump_file&.dirname&.mkpath
18
- File.write("./tmp/claude-messages.log", "#{json}\n", mode: "a") if raw_dump_file
17
+ if raw_dump_file
18
+ raw_dump_file.dirname.mkpath
19
+ File.write(raw_dump_file.to_s, "#{json}\n", mode: "a")
20
+ end
19
21
  from_hash(JSON.parse(json, symbolize_names: true))
20
22
  end
21
23
 
@@ -100,6 +100,21 @@ module Roast
100
100
  lines.join("\n")
101
101
  end
102
102
 
103
+ # Get the statistics as a hash, with aggregate and per-model usage as nested hashes
104
+ #
105
+ # #### See Also
106
+ # - `Agent::Usage#to_h`
107
+ #
108
+ #: () -> Hash[Symbol, untyped]
109
+ def to_h
110
+ {
111
+ duration_ms:,
112
+ num_turns:,
113
+ usage: usage.to_h,
114
+ model_usage: model_usage.transform_values(&:to_h),
115
+ }
116
+ end
117
+
103
118
  private
104
119
 
105
120
  #: (Numeric?, Numeric?) -> Numeric?
@@ -68,6 +68,13 @@ module Roast
68
68
  result
69
69
  end
70
70
 
71
+ # Get the token counts and cost as a hash
72
+ #
73
+ #: () -> Hash[Symbol, untyped]
74
+ def to_h
75
+ { input_tokens:, output_tokens:, cost_usd: }
76
+ end
77
+
71
78
  private
72
79
 
73
80
  #: (Numeric?, Numeric?) -> Numeric?
@@ -45,17 +45,70 @@ module Roast
45
45
  # The agent may make multiple turns (back-and-forth exchanges) during execution,
46
46
  # especially when using tools. Each turn is counted in the execution statistics.
47
47
  #
48
+ # If the cog is configured with `record_agent_runs_to`, a record of the run is appended to that file
49
+ # whether the run succeeds or fails.
50
+ #
48
51
  #: (Input) -> Output
49
52
  def execute(input)
50
- output = provider.invoke(input)
51
- if config.show_stats?
52
- Event << { block: { header: "AGENT STATS", content: "#{output.stats}\nSession ID: #{output.session}" } }
53
+ started_at = Time.now
54
+ begin
55
+ output = provider.invoke(input)
56
+ if config.show_stats?
57
+ Event << { block: { header: "AGENT STATS", content: "#{output.stats}\nSession ID: #{output.session}" } }
58
+ end
59
+ output
60
+ rescue StandardError => e
61
+ error = e
62
+ raise
63
+ ensure
64
+ record_run(input, output, error, started_at) if config.valid_record_agent_runs_to_path
53
65
  end
54
- output
55
66
  end
56
67
 
57
68
  private
58
69
 
70
+ #: (Input, Output?, StandardError?, Time) -> void
71
+ def record_run(input, output, error, started_at)
72
+ record = {
73
+ started_at: started_at.utc.iso8601(3),
74
+ finished_at: Time.now.utc.iso8601(3),
75
+ cog: { name: anonymous? ? nil : name, path: record_path },
76
+ provider: config.valid_provider!,
77
+ model: config.valid_model,
78
+ prompts: input.prompts,
79
+ system_prompt: { append: config.valid_append_system_prompt, replace: config.valid_replace_system_prompt },
80
+ status: run_status(output, error),
81
+ error: error && { class: error.class.name, message: error.message },
82
+ response: output&.response,
83
+ session: output&.session,
84
+ stats: output&.stats&.to_h,
85
+ }
86
+ path = config.valid_record_agent_runs_to_path.not_nil!
87
+ path.dirname.mkpath
88
+ File.write(path.to_s, "#{JSON.generate(record)}\n", mode: "a")
89
+ end
90
+
91
+ #: () -> Array[Hash[Symbol, untyped]]
92
+ def record_path
93
+ TaskContext.path.filter_map do |element|
94
+ cog = element.cog
95
+ execution_manager = element.execution_manager
96
+ if cog.present?
97
+ { cog: cog.type, name: cog.anonymous? ? nil : cog.name }
98
+ elsif execution_manager&.scope
99
+ { scope: execution_manager.scope, index: execution_manager.scope_index }
100
+ end
101
+ end
102
+ end
103
+
104
+ #: (Output?, StandardError?) -> String
105
+ def run_status(output, error)
106
+ return "timed_out" if error.is_a?(CommandRunner::TimeoutError)
107
+ return "failed" if error || output.nil?
108
+
109
+ "succeeded"
110
+ end
111
+
59
112
  #: () -> Provider
60
113
  def provider
61
114
  @provider ||= case config.valid_provider!
@@ -168,6 +168,40 @@ module Roast
168
168
  show_stdout? || show_stderr?
169
169
  end
170
170
 
171
+ # Configure the cog to run the command with these environment variables set
172
+ #
173
+ # Variables not named here are inherited from Roast's own environment. A `nil` value unsets
174
+ # the variable for the command.
175
+ #
176
+ # #### See Also
177
+ # - `no_env!`
178
+ #
179
+ #: (Hash[String | Symbol, String?]) -> void
180
+ def env(vars)
181
+ @values[:env] = vars
182
+ end
183
+
184
+ # Configure the cog to run the command with Roast's own environment, unchanged (the default)
185
+ #
186
+ # #### See Also
187
+ # - `env`
188
+ #
189
+ #: () -> void
190
+ def no_env!
191
+ @values[:env] = nil
192
+ end
193
+
194
+ # Get the validated environment variables the command will be run with, keyed by name
195
+ #
196
+ # #### See Also
197
+ # - `env`
198
+ # - `no_env!`
199
+ #
200
+ #: () -> Hash[String, String?]
201
+ def valid_env
202
+ (@values[:env] || {}).to_h { |name, value| [name.to_s, value&.to_s] }
203
+ end
204
+
171
205
  alias_method(:quiet!, :no_display!)
172
206
  end
173
207
 
@@ -281,6 +315,7 @@ module Roast
281
315
  .execute(
282
316
  [input.command] + input.args,
283
317
  working_directory: config.valid_working_directory,
318
+ env: config.valid_env,
284
319
  stdin_content: input.stdin,
285
320
  stdout_handler: stdout_handler,
286
321
  stderr_handler: stderr_handler,
@@ -45,6 +45,7 @@ module Roast
45
45
  #: (
46
46
  #| Array[String],
47
47
  #| ?working_directory: (Pathname | String)?,
48
+ #| ?env: Hash[String, String?],
48
49
  #| ?timeout: (Integer | Float)?,
49
50
  #| ?stdin_content: String?,
50
51
  #| ?stdout_handler: (^(String) -> void)?,
@@ -53,6 +54,7 @@ module Roast
53
54
  def execute(
54
55
  args,
55
56
  working_directory: nil,
57
+ env: {},
56
58
  timeout: nil,
57
59
  stdin_content: nil,
58
60
  stdout_handler: nil,
@@ -69,7 +71,7 @@ module Roast
69
71
  stdin, stdout, stderr, wait_thread = Bundler.with_unbundled_env do
70
72
  Open3 #: as untyped
71
73
  .popen3(
72
- env_overrides.merge({ "PWD" => working_directory&.to_s }).compact,
74
+ env.merge(env_overrides, { "PWD" => working_directory&.to_s }.compact),
73
75
  *args.map(&:to_s),
74
76
  { chdir: working_directory }.compact,
75
77
  )
data/lib/roast/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Roast
5
- VERSION = "1.3.0"
5
+ VERSION = "1.4.0"
6
6
  end
@@ -184,6 +184,10 @@ module Roast
184
184
  # #### Configure debugging
185
185
  # - `dump_raw_agent_messages_to(filename)` - Dump raw agent messages to a file for debugging
186
186
  #
187
+ # #### Configure run records
188
+ # - `record_agent_runs_to(filename)` - Append a JSON record of each agent run (prompts, response, session, stats, status) to a file
189
+ # - `no_record_agent_runs!` - Don't record agent runs (default)
190
+ #
187
191
  # #### Configure asynchronous execution
188
192
  # - `async!` - Run the cog asynchronously in the background
189
193
  # - `no_async!` (alias `sync!`) - Run the cog synchronously (default)
@@ -273,6 +277,10 @@ module Roast
273
277
  # - `fail_on_error!` - Consider the cog failed if the command returns a non-zero exit status (default)
274
278
  # - `no_fail_on_error!` - Don't fail the cog on non-zero exit status (exit status still available in output)
275
279
  #
280
+ # #### Configure the environment
281
+ # - `env(hash)` - Set environment variables for the command (a `nil` value unsets one)
282
+ # - `no_env!` - Run the command with Roast's own environment, unchanged (default)
283
+ #
276
284
  # #### Configure display output
277
285
  # - `show_stdout!` - Write STDOUT to the console
278
286
  # - `no_show_stdout!` - Don't write STDOUT to the console (default)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roast-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -408,7 +408,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
408
408
  - !ruby/object:Gem::Version
409
409
  version: '0'
410
410
  requirements: []
411
- rubygems_version: 4.0.21
411
+ rubygems_version: 4.0.22
412
412
  specification_version: 4
413
413
  summary: A framework for executing structured AI workflows in Ruby
414
414
  test_files: []