roast-ai 1.2.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 +4 -4
- data/CONTRIBUTING.md +0 -2
- data/Gemfile.lock +8 -8
- data/README.md +26 -14
- data/examples/demo/Gemfile.lock +2 -2
- data/examples/plugin-gem-example/Gemfile.lock +4 -4
- data/examples/simple_agent.rb +1 -1
- data/examples/targets_and_params.rb +12 -0
- data/internal/documentation/comments/doc-comments-external.md +5 -3
- data/internal/documentation/comments/doc-comments.md +2 -1
- data/lib/roast/cog_input_manager.rb +2 -44
- data/lib/roast/cogs/agent/config.rb +103 -4
- data/lib/roast/cogs/agent/providers/claude/claude_invocation.rb +5 -1
- data/lib/roast/cogs/agent/providers/claude/message.rb +4 -2
- data/lib/roast/cogs/agent/providers/pi/messages/tool_call_message.rb +218 -18
- data/lib/roast/cogs/agent/providers/pi/messages/tool_result_message.rb +208 -13
- data/lib/roast/cogs/agent/providers/pi/pi_invocation.rb +2 -0
- data/lib/roast/cogs/agent/stats.rb +15 -0
- data/lib/roast/cogs/agent/usage.rb +7 -0
- data/lib/roast/cogs/agent.rb +57 -4
- data/lib/roast/cogs/chat/config.rb +53 -2
- data/lib/roast/cogs/chat.rb +13 -5
- data/lib/roast/cogs/cmd.rb +35 -0
- data/lib/roast/command_runner.rb +3 -1
- data/lib/roast/config_manager.rb +29 -2
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow.rb +10 -7
- data/lib/roast/workflow_param_accessors.rb +53 -0
- data/sorbet/rbi/shims/lib/roast/cog/config.rbi +251 -0
- data/sorbet/rbi/shims/lib/roast/config_context.rbi +13 -1
- data/tutorial/01_your_first_workflow/README.md +3 -2
- data/tutorial/01_your_first_workflow/configured_chat.rb +1 -1
- data/tutorial/02_chaining_cogs/README.md +23 -2
- data/tutorial/02_chaining_cogs/code_review.rb +2 -2
- data/tutorial/02_chaining_cogs/session_resumption.rb +1 -0
- data/tutorial/03_targets_and_params/README.md +10 -1
- data/tutorial/04_configuration_options/README.md +19 -0
- data/tutorial/README.md +1 -1
- metadata +4 -2
|
@@ -5,6 +5,14 @@ module Roast
|
|
|
5
5
|
module Cogs
|
|
6
6
|
class Chat < Cog
|
|
7
7
|
class Config < Cog::Config
|
|
8
|
+
# Environment variable that overrides the built-in default chat provider.
|
|
9
|
+
#
|
|
10
|
+
# When a chat cog does not explicitly configure a provider, Roast uses the provider named by
|
|
11
|
+
# this variable, falling back to the built-in default (`PROVIDERS.keys.first`, i.e. `:openai`) when it
|
|
12
|
+
# is unset or blank. The value is normalized (surrounding whitespace stripped, then downcased)
|
|
13
|
+
# before lookup, and an explicit `provider` configured on the cog always takes precedence over it.
|
|
14
|
+
DEFAULT_PROVIDER_ENV_VAR = "ROAST_DEFAULT_CHAT_PROVIDER" #: String
|
|
15
|
+
|
|
8
16
|
PROVIDERS = {
|
|
9
17
|
openai: {
|
|
10
18
|
api_key_env_var: "OPENAI_API_KEY",
|
|
@@ -28,6 +36,10 @@ module Roast
|
|
|
28
36
|
default_base_url: "https://generativelanguage.googleapis.com/v1beta",
|
|
29
37
|
default_model: "gemini-3.1-flash-lite",
|
|
30
38
|
},
|
|
39
|
+
bedrock: {
|
|
40
|
+
api_key_env_var: "AWS_ACCESS_KEY_ID",
|
|
41
|
+
default_model: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
42
|
+
},
|
|
31
43
|
}.freeze #: Hash[Symbol, Hash[Symbol, String]]
|
|
32
44
|
|
|
33
45
|
# Configure the cog to use a specified API provider when invoking the llm
|
|
@@ -42,7 +54,8 @@ module Roast
|
|
|
42
54
|
|
|
43
55
|
# Configure the cog to use the default provider when invoking the llm
|
|
44
56
|
#
|
|
45
|
-
# The default LLM provider
|
|
57
|
+
# The default LLM provider is the one named by the `ROAST_DEFAULT_CHAT_PROVIDER` environment
|
|
58
|
+
# variable, or OpenAI (`:openai`) when that variable is unset.
|
|
46
59
|
#
|
|
47
60
|
# #### See Also
|
|
48
61
|
# - `provider`
|
|
@@ -54,6 +67,10 @@ module Roast
|
|
|
54
67
|
|
|
55
68
|
# Get the validated provider name that the cog is configured to use when invoking the llm
|
|
56
69
|
#
|
|
70
|
+
# The provider is resolved in order of precedence: the provider explicitly configured on the cog,
|
|
71
|
+
# then the `ROAST_DEFAULT_CHAT_PROVIDER` environment variable (normalized by stripping surrounding
|
|
72
|
+
# whitespace and downcasing), then the built-in default (`PROVIDERS.keys.first`, i.e. `:openai`).
|
|
73
|
+
#
|
|
57
74
|
# Note: this method will return the name of a valid provider or raise an `InvalidConfigError`.
|
|
58
75
|
# It will __not__, however, validate that the you have access to the provider's API.
|
|
59
76
|
# If you have not correctly configured API access, you will likely experience a failure when Roast attempts to
|
|
@@ -65,7 +82,8 @@ module Roast
|
|
|
65
82
|
#
|
|
66
83
|
#: () -> Symbol
|
|
67
84
|
def valid_provider!
|
|
68
|
-
|
|
85
|
+
env_default = ENV[DEFAULT_PROVIDER_ENV_VAR].presence&.strip&.downcase&.to_sym
|
|
86
|
+
provider = @values[:provider] || env_default || PROVIDERS.keys.first
|
|
69
87
|
unless PROVIDERS.include?(provider)
|
|
70
88
|
raise InvalidConfigError, "'#{provider}' is not a valid provider. Available providers include: #{PROVIDERS.keys.join(", ")}"
|
|
71
89
|
end
|
|
@@ -94,6 +112,7 @@ module Roast
|
|
|
94
112
|
# - Anthropic Provider: ANTHROPIC_API_KEY
|
|
95
113
|
# - Perplexity Provider: PERPLEXITY_API_KEY
|
|
96
114
|
# - Gemini Provider: GEMINI_API_KEY
|
|
115
|
+
# - Bedrock Provider: AWS_ACCESS_KEY_ID
|
|
97
116
|
#
|
|
98
117
|
# #### See Also
|
|
99
118
|
# - `api_key`
|
|
@@ -113,6 +132,7 @@ module Roast
|
|
|
113
132
|
# - Anthropic Provider: ANTHROPIC_API_KEY
|
|
114
133
|
# - Perplexity Provider: PERPLEXITY_API_KEY
|
|
115
134
|
# - Gemini Provider: GEMINI_API_KEY
|
|
135
|
+
# - Bedrock Provider: AWS_ACCESS_KEY_ID
|
|
116
136
|
#
|
|
117
137
|
# #### See Also
|
|
118
138
|
# - `api_key`
|
|
@@ -176,6 +196,37 @@ module Roast
|
|
|
176
196
|
PROVIDERS.dig(valid_provider!, :default_base_url)
|
|
177
197
|
end
|
|
178
198
|
|
|
199
|
+
# Get the AWS secret access key used to sign requests when the Bedrock provider is selected
|
|
200
|
+
#
|
|
201
|
+
# Only the Bedrock provider uses this value. It is read from the `AWS_SECRET_ACCESS_KEY` environment variable.
|
|
202
|
+
# This method will raise InvalidConfigError if that variable is unset or blank.
|
|
203
|
+
#
|
|
204
|
+
#: () -> String
|
|
205
|
+
def valid_aws_secret_access_key!
|
|
206
|
+
ENV["AWS_SECRET_ACCESS_KEY"].presence || raise(InvalidConfigError, "AWS_SECRET_ACCESS_KEY is not set")
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Get the AWS session token used to sign requests with temporary credentials, such as those from AWS SSO
|
|
210
|
+
#
|
|
211
|
+
# Only the Bedrock provider uses this value. It is read from the `AWS_SESSION_TOKEN` environment variable.
|
|
212
|
+
# Returns nil if that variable is unset or blank, because long-term credentials need no session token.
|
|
213
|
+
#
|
|
214
|
+
#: () -> String?
|
|
215
|
+
def valid_aws_session_token
|
|
216
|
+
ENV["AWS_SESSION_TOKEN"].presence
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Get the AWS region that the Bedrock provider sends requests to
|
|
220
|
+
#
|
|
221
|
+
# Only the Bedrock provider uses this value. It is read from the `AWS_REGION` environment variable only.
|
|
222
|
+
# `AWS_DEFAULT_REGION` is ignored, so that a region left in a shell profile never selects a region silently.
|
|
223
|
+
# This method will raise InvalidConfigError if `AWS_REGION` is unset or blank.
|
|
224
|
+
#
|
|
225
|
+
#: () -> String
|
|
226
|
+
def valid_aws_region!
|
|
227
|
+
ENV["AWS_REGION"].presence || raise(InvalidConfigError, "AWS_REGION is not set")
|
|
228
|
+
end
|
|
229
|
+
|
|
179
230
|
# Configure the cog to use a specific model when invoking the agent
|
|
180
231
|
#
|
|
181
232
|
# The model name format is provider-specific.
|
data/lib/roast/cogs/chat.rb
CHANGED
|
@@ -59,16 +59,19 @@ module Roast
|
|
|
59
59
|
Event << { block: { header: "UNKNOWN", content: message.content } } if config.show_prompt? || config.show_response?
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
|
+
# The Bedrock Converse response has no modelId field, so ruby_llm leaves response.model_id nil for it.
|
|
63
|
+
# The chat model holds the ID that ruby_llm sent.
|
|
64
|
+
model_id = response.model_id || chat.model.id
|
|
62
65
|
if config.show_stats?
|
|
63
66
|
temperature = chat.instance_variable_get(:@temperature)
|
|
64
|
-
lines = ["Model: #{
|
|
67
|
+
lines = ["Model: #{model_id}"]
|
|
65
68
|
lines << "Temperature: #{format("%0.2f", temperature)}" if temperature
|
|
66
69
|
lines << "Input Tokens: #{response.input_tokens}"
|
|
67
70
|
lines << "Output Tokens: #{response.output_tokens}"
|
|
68
71
|
Event << { block: { header: "LLM STATS", content: lines.join("\n") } }
|
|
69
72
|
end
|
|
70
73
|
|
|
71
|
-
verify_response_not_truncated!(response)
|
|
74
|
+
verify_response_not_truncated!(response, model_id)
|
|
72
75
|
|
|
73
76
|
Output.new(Session.from_chat(chat), response.content)
|
|
74
77
|
end
|
|
@@ -95,15 +98,15 @@ module Roast
|
|
|
95
98
|
# on a natural boundary. This is an unavoidable trade-off of the heuristic approach until
|
|
96
99
|
# ruby_llm exposes finish_reason.
|
|
97
100
|
#
|
|
98
|
-
#: (RubyLLM::Message) -> void
|
|
99
|
-
def verify_response_not_truncated!(response)
|
|
101
|
+
#: (RubyLLM::Message, String) -> void
|
|
102
|
+
def verify_response_not_truncated!(response, model_id)
|
|
100
103
|
max_tokens = effective_max_tokens
|
|
101
104
|
return unless max_tokens
|
|
102
105
|
return unless response.output_tokens
|
|
103
106
|
|
|
104
107
|
if response.output_tokens >= max_tokens
|
|
105
108
|
raise MaxTokensExceededError,
|
|
106
|
-
"LLM response from #{
|
|
109
|
+
"LLM response from #{model_id} was truncated at the max token limit " \
|
|
107
110
|
"(output: #{response.output_tokens} tokens, limit: #{max_tokens} tokens). " \
|
|
108
111
|
"The response content is likely incomplete and should not be used."
|
|
109
112
|
end
|
|
@@ -143,6 +146,11 @@ module Roast
|
|
|
143
146
|
when :gemini
|
|
144
147
|
context.gemini_api_key = config.valid_api_key!
|
|
145
148
|
context.gemini_api_base = config.valid_base_url
|
|
149
|
+
when :bedrock
|
|
150
|
+
context.bedrock_api_key = config.valid_api_key!
|
|
151
|
+
context.bedrock_secret_key = config.valid_aws_secret_access_key!
|
|
152
|
+
context.bedrock_session_token = config.valid_aws_session_token
|
|
153
|
+
context.bedrock_region = config.valid_aws_region!
|
|
146
154
|
end
|
|
147
155
|
end
|
|
148
156
|
end
|
data/lib/roast/cogs/cmd.rb
CHANGED
|
@@ -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,
|
data/lib/roast/command_runner.rb
CHANGED
|
@@ -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
|
-
|
|
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/config_manager.rb
CHANGED
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
module Roast
|
|
5
5
|
class ConfigManager
|
|
6
|
+
include WorkflowParamAccessors
|
|
7
|
+
|
|
6
8
|
class ConfigManagerError < Roast::Error; end
|
|
7
9
|
class ConfigManagerNotPreparedError < ConfigManagerError; end
|
|
8
10
|
class ConfigManagerAlreadyPreparedError < ConfigManagerError; end
|
|
9
11
|
class IllegalCogNameError < ConfigManagerError; end
|
|
10
12
|
|
|
11
|
-
#: (Cog::Registry, Array[^() -> void]) -> void
|
|
12
|
-
def initialize(cog_registry, config_procs)
|
|
13
|
+
#: (Cog::Registry, Array[^() -> void], WorkflowContext) -> void
|
|
14
|
+
def initialize(cog_registry, config_procs, workflow_context)
|
|
13
15
|
@cog_registry = cog_registry
|
|
14
16
|
@config_procs = config_procs
|
|
17
|
+
@workflow_context = workflow_context
|
|
15
18
|
@config_context = ConfigContext.new #: ConfigContext
|
|
16
19
|
@global_config = Cog::Config.new #: Cog::Config
|
|
17
20
|
@general_configs = {} #: Hash[singleton(Cog), Cog::Config]
|
|
@@ -117,6 +120,7 @@ module Roast
|
|
|
117
120
|
# NOTE: Sorbet expects the proc passed to instance_exec to be declared as taking an argument
|
|
118
121
|
# but our cog_config_proc does not get an argument
|
|
119
122
|
cog_config_proc = cog_config_proc #: as ^(untyped) -> void
|
|
123
|
+
bind_workflow_params(config_object)
|
|
120
124
|
config_object.instance_exec(&cog_config_proc) if cog_config_proc
|
|
121
125
|
nil
|
|
122
126
|
end
|
|
@@ -134,8 +138,31 @@ module Roast
|
|
|
134
138
|
#: (^() -> void ) -> void
|
|
135
139
|
def on_global(global_config_proc)
|
|
136
140
|
global_config_proc = global_config_proc #: as ^(untyped) -> void
|
|
141
|
+
bind_workflow_params(@global_config)
|
|
137
142
|
@global_config.instance_exec(&global_config_proc) if global_config_proc
|
|
138
143
|
nil
|
|
139
144
|
end
|
|
145
|
+
|
|
146
|
+
#: (Cog::Config) -> void
|
|
147
|
+
def bind_workflow_params(object)
|
|
148
|
+
target_bang_method = method(:target!)
|
|
149
|
+
targets_method = method(:targets)
|
|
150
|
+
arg_question_method = method(:arg?)
|
|
151
|
+
args_method = method(:args)
|
|
152
|
+
kwarg_method = method(:kwarg)
|
|
153
|
+
kwarg_bang_method = method(:kwarg!)
|
|
154
|
+
kwarg_question_method = method(:kwarg?)
|
|
155
|
+
kwargs_method = method(:kwargs)
|
|
156
|
+
object.instance_eval do
|
|
157
|
+
define_singleton_method(:target!, proc { target_bang_method.call })
|
|
158
|
+
define_singleton_method(:targets, proc { targets_method.call })
|
|
159
|
+
define_singleton_method(:arg?, proc { |value| arg_question_method.call(value) })
|
|
160
|
+
define_singleton_method(:args, proc { args_method.call })
|
|
161
|
+
define_singleton_method(:kwarg, proc { |key| kwarg_method.call(key) })
|
|
162
|
+
define_singleton_method(:kwarg!, proc { |key| kwarg_bang_method.call(key) })
|
|
163
|
+
define_singleton_method(:kwarg?, proc { |key| kwarg_question_method.call(key) })
|
|
164
|
+
define_singleton_method(:kwargs, proc { kwargs_method.call })
|
|
165
|
+
end
|
|
166
|
+
end
|
|
140
167
|
end
|
|
141
168
|
end
|
data/lib/roast/version.rb
CHANGED
data/lib/roast/workflow.rb
CHANGED
|
@@ -19,12 +19,15 @@ module Roast
|
|
|
19
19
|
Sync do
|
|
20
20
|
Dir.mktmpdir("roast-") do |tmpdir|
|
|
21
21
|
EventMonitor.start!
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
begin
|
|
23
|
+
workflow_dir = Pathname.new(workflow_path).dirname
|
|
24
|
+
workflow_context = WorkflowContext.new(params: params, tmpdir: tmpdir, workflow_dir: workflow_dir)
|
|
25
|
+
workflow = new(workflow_path, workflow_context)
|
|
26
|
+
workflow.prepare!
|
|
27
|
+
workflow.start!
|
|
28
|
+
ensure
|
|
29
|
+
EventMonitor.stop! if EventMonitor.running?
|
|
30
|
+
end
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
end
|
|
@@ -48,7 +51,7 @@ module Roast
|
|
|
48
51
|
|
|
49
52
|
@preparing = true
|
|
50
53
|
extract_dsl_procs!
|
|
51
|
-
@config_manager = ConfigManager.new(@cog_registry, @config_procs)
|
|
54
|
+
@config_manager = ConfigManager.new(@cog_registry, @config_procs, @workflow_context)
|
|
52
55
|
@config_manager.not_nil!.prepare!
|
|
53
56
|
# TODO: probably we should just not pass the params as the top-level scope value anymore
|
|
54
57
|
@execution_manager = ExecutionManager.new(@cog_registry, @config_manager.not_nil!, @execution_procs, @workflow_context, scope_value: @workflow_context.params)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Roast
|
|
5
|
+
# @requires_ancestor: Kernel
|
|
6
|
+
module WorkflowParamAccessors
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
#: () -> String
|
|
10
|
+
def target!
|
|
11
|
+
raise ArgumentError, "expected exactly one target" unless @workflow_context.params.targets.length == 1
|
|
12
|
+
|
|
13
|
+
@workflow_context.params.targets.first #: as String
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#: () -> Array[String]
|
|
17
|
+
def targets
|
|
18
|
+
@workflow_context.params.targets.dup
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: (Symbol) -> bool
|
|
22
|
+
def arg?(value)
|
|
23
|
+
@workflow_context.params.args.include?(value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#: () -> Array[Symbol]
|
|
27
|
+
def args
|
|
28
|
+
@workflow_context.params.args.dup
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#: (Symbol) -> String?
|
|
32
|
+
def kwarg(key)
|
|
33
|
+
@workflow_context.params.kwargs[key]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#: (Symbol) -> String
|
|
37
|
+
def kwarg!(key)
|
|
38
|
+
raise ArgumentError, "expected keyword argument '#{key}' to be present" unless @workflow_context.params.kwargs.include?(key)
|
|
39
|
+
|
|
40
|
+
@workflow_context.params.kwargs[key] #: as String
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#: (Symbol) -> bool
|
|
44
|
+
def kwarg?(key)
|
|
45
|
+
@workflow_context.params.kwargs.include?(key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
#: () -> Hash[Symbol, String]
|
|
49
|
+
def kwargs
|
|
50
|
+
@workflow_context.params.kwargs.dup
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Roast
|
|
5
|
+
class Cog
|
|
6
|
+
class Config
|
|
7
|
+
########################################
|
|
8
|
+
# Workflow Methods
|
|
9
|
+
########################################
|
|
10
|
+
|
|
11
|
+
# Get the single target value passed to the workflow
|
|
12
|
+
#
|
|
13
|
+
# Returns the target when exactly one target was provided to the workflow. Raises an
|
|
14
|
+
# `ArgumentError` if the workflow was invoked with zero or multiple targets.
|
|
15
|
+
#
|
|
16
|
+
# Targets are file paths, URLs, or other identifiers passed to the workflow when it is
|
|
17
|
+
# invoked. Use this method when your workflow expects exactly one target.
|
|
18
|
+
#
|
|
19
|
+
# ### Invocation
|
|
20
|
+
# Invoke a workflow with a single target like this:
|
|
21
|
+
# ```bash
|
|
22
|
+
# roast execute my_workflow.rb my_target_file.txt
|
|
23
|
+
# ```
|
|
24
|
+
#
|
|
25
|
+
# ### Usage
|
|
26
|
+
# ```ruby
|
|
27
|
+
# config do
|
|
28
|
+
# chat do
|
|
29
|
+
# # Configure based on the single target
|
|
30
|
+
# temperature(target!.end_with?(".md") ? 0.7 : 0.2)
|
|
31
|
+
# end
|
|
32
|
+
# end
|
|
33
|
+
# ```
|
|
34
|
+
#
|
|
35
|
+
# #### See Also
|
|
36
|
+
# - `targets` - Get all targets as an array (works with any number of targets)
|
|
37
|
+
#
|
|
38
|
+
#: () -> String
|
|
39
|
+
def target!; end
|
|
40
|
+
|
|
41
|
+
# Get all targets passed to the workflow
|
|
42
|
+
#
|
|
43
|
+
# Returns an array of all targets provided to the workflow. Works with any number of targets
|
|
44
|
+
# (zero, one, or many). Targets are file paths, URLs, or other identifiers passed when the
|
|
45
|
+
# workflow is invoked.
|
|
46
|
+
#
|
|
47
|
+
# ### Invocation
|
|
48
|
+
# Invoke a workflow with multiple targets like this:
|
|
49
|
+
# ```bash
|
|
50
|
+
# roast execute my_workflow.rb target_one.txt target_two.txt
|
|
51
|
+
# ```
|
|
52
|
+
# or using shell globs like this:
|
|
53
|
+
# ```bash
|
|
54
|
+
# roast execute my_workflow.rb target_*.txt
|
|
55
|
+
# ```
|
|
56
|
+
#
|
|
57
|
+
# ### Usage
|
|
58
|
+
# ```ruby
|
|
59
|
+
# config do
|
|
60
|
+
# map do
|
|
61
|
+
# # Process targets in parallel when there are several
|
|
62
|
+
# parallel! if targets.length > 3
|
|
63
|
+
# end
|
|
64
|
+
# end
|
|
65
|
+
# ```
|
|
66
|
+
#
|
|
67
|
+
# #### See Also
|
|
68
|
+
# - `target!` - Get the single target (raises an error if there isn't exactly one)
|
|
69
|
+
#
|
|
70
|
+
#: () -> Array[String]
|
|
71
|
+
def targets; end
|
|
72
|
+
|
|
73
|
+
# Check if a flag argument was passed to the workflow
|
|
74
|
+
#
|
|
75
|
+
# Returns `true` if the specified flag argument symbol was provided when the workflow
|
|
76
|
+
# was invoked, `false` otherwise.
|
|
77
|
+
#
|
|
78
|
+
# Flag arguments are symbolic flags passed to the workflow (e.g., `retry`, `force`)
|
|
79
|
+
# that enable or modify behavior.
|
|
80
|
+
#
|
|
81
|
+
# ### Invocation
|
|
82
|
+
# Invoke a workflow with flag arguments like this:
|
|
83
|
+
# ```bash
|
|
84
|
+
# roast execute my_workflow.rb [TARGETS] -- retry force
|
|
85
|
+
# ```
|
|
86
|
+
#
|
|
87
|
+
# ### Usage
|
|
88
|
+
# ```ruby
|
|
89
|
+
# config do
|
|
90
|
+
# chat do
|
|
91
|
+
# # Pick a cheaper model when invoked with the `fast` flag
|
|
92
|
+
# model(arg?(:fast) ? "gpt-5.4-nano" : "gpt-5")
|
|
93
|
+
# end
|
|
94
|
+
# end
|
|
95
|
+
# ```
|
|
96
|
+
#
|
|
97
|
+
# #### See Also
|
|
98
|
+
# - `args` - Get all flag arguments as an array
|
|
99
|
+
# - `kwarg?` - Check for keyword arguments (key-value pairs)
|
|
100
|
+
#
|
|
101
|
+
#: (Symbol) -> bool
|
|
102
|
+
def arg?(value); end
|
|
103
|
+
|
|
104
|
+
# Get all flag arguments passed to the workflow
|
|
105
|
+
#
|
|
106
|
+
# Returns an array of all flag argument symbols provided when the workflow was invoked.
|
|
107
|
+
# Flag arguments are symbolic flags (e.g., `retry`, `force`) that enable or modify
|
|
108
|
+
# workflow behavior.
|
|
109
|
+
#
|
|
110
|
+
# ### Invocation
|
|
111
|
+
# Invoke a workflow with flag arguments like this:
|
|
112
|
+
# ```bash
|
|
113
|
+
# roast execute my_workflow.rb [TARGETS] -- retry force
|
|
114
|
+
# ```
|
|
115
|
+
#
|
|
116
|
+
# ### Usage
|
|
117
|
+
# ```ruby
|
|
118
|
+
# config do
|
|
119
|
+
# global do
|
|
120
|
+
# # Show full output whenever any flags were passed
|
|
121
|
+
# display! unless args.empty?
|
|
122
|
+
# end
|
|
123
|
+
# end
|
|
124
|
+
# ```
|
|
125
|
+
#
|
|
126
|
+
# #### See Also
|
|
127
|
+
# - `arg?` - Check if a specific flag argument was provided
|
|
128
|
+
# - `kwargs` - Get all keyword arguments
|
|
129
|
+
#
|
|
130
|
+
#: () -> Array[Symbol]
|
|
131
|
+
def args; end
|
|
132
|
+
|
|
133
|
+
# Get a keyword argument value passed to the workflow
|
|
134
|
+
#
|
|
135
|
+
# Returns the string value for the specified keyword argument key, or `nil` if the key was
|
|
136
|
+
# not provided. Keyword arguments are key-value pairs passed to the workflow for configuration.
|
|
137
|
+
#
|
|
138
|
+
# ### Invocation
|
|
139
|
+
# Invoke a workflow with keyword arguments like this:
|
|
140
|
+
# ```bash
|
|
141
|
+
# roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
|
|
142
|
+
# ```
|
|
143
|
+
#
|
|
144
|
+
# ### Usage
|
|
145
|
+
# ```ruby
|
|
146
|
+
# config do
|
|
147
|
+
# chat do
|
|
148
|
+
# # Use a provided model override, or fall back to a default
|
|
149
|
+
# model(kwarg(:model) || "gpt-5")
|
|
150
|
+
# end
|
|
151
|
+
# end
|
|
152
|
+
# ```
|
|
153
|
+
#
|
|
154
|
+
# #### See Also
|
|
155
|
+
# - `kwarg!` - Get a keyword argument value (raises an error if not provided)
|
|
156
|
+
# - `kwarg?` - Check if a keyword argument was provided
|
|
157
|
+
# - `kwargs` - Get all keyword arguments as a hash
|
|
158
|
+
#
|
|
159
|
+
#: (Symbol) -> String?
|
|
160
|
+
def kwarg(key); end
|
|
161
|
+
|
|
162
|
+
# Get a required keyword argument value passed to the workflow
|
|
163
|
+
#
|
|
164
|
+
# Returns the string value for the specified keyword argument key. Raises an `ArgumentError`
|
|
165
|
+
# if the key was not provided.
|
|
166
|
+
#
|
|
167
|
+
# Use this when your workflow requires a specific keyword argument to function correctly.
|
|
168
|
+
#
|
|
169
|
+
# ### Invocation
|
|
170
|
+
# Invoke a workflow with keyword arguments like this:
|
|
171
|
+
# ```bash
|
|
172
|
+
# roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
|
|
173
|
+
# ```
|
|
174
|
+
#
|
|
175
|
+
# ### Usage
|
|
176
|
+
# ```ruby
|
|
177
|
+
# config do
|
|
178
|
+
# chat do
|
|
179
|
+
# # Require the 'model' keyword argument
|
|
180
|
+
# model(kwarg!(:model))
|
|
181
|
+
# end
|
|
182
|
+
# end
|
|
183
|
+
# ```
|
|
184
|
+
#
|
|
185
|
+
# #### See Also
|
|
186
|
+
# - `kwarg` - Get a keyword argument value (returns nil if not provided)
|
|
187
|
+
# - `kwarg?` - Check if a keyword argument was provided
|
|
188
|
+
# - `kwargs` - Get all keyword arguments as a hash
|
|
189
|
+
#
|
|
190
|
+
#: (Symbol) -> String
|
|
191
|
+
def kwarg!(key); end
|
|
192
|
+
|
|
193
|
+
# Check if a keyword argument was passed to the workflow
|
|
194
|
+
#
|
|
195
|
+
# Returns `true` if the specified keyword argument key was provided when the workflow was
|
|
196
|
+
# invoked, `false` otherwise.
|
|
197
|
+
#
|
|
198
|
+
# ### Invocation
|
|
199
|
+
# Invoke a workflow with keyword arguments like this:
|
|
200
|
+
# ```bash
|
|
201
|
+
# roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
|
|
202
|
+
# ```
|
|
203
|
+
#
|
|
204
|
+
# ### Usage
|
|
205
|
+
# ```ruby
|
|
206
|
+
# config do
|
|
207
|
+
# chat do
|
|
208
|
+
# temperature(0.9) if kwarg?(:creative)
|
|
209
|
+
# end
|
|
210
|
+
# end
|
|
211
|
+
# ```
|
|
212
|
+
#
|
|
213
|
+
# #### See Also
|
|
214
|
+
# - `kwarg` - Get a keyword argument value (returns nil if not provided)
|
|
215
|
+
# - `kwarg!` - Get a keyword argument value (raises an error if not provided)
|
|
216
|
+
# - `kwargs` - Get all keyword arguments as a hash
|
|
217
|
+
#
|
|
218
|
+
#: (Symbol) -> bool
|
|
219
|
+
def kwarg?(key); end
|
|
220
|
+
|
|
221
|
+
# Get all keyword arguments passed to the workflow
|
|
222
|
+
#
|
|
223
|
+
# Returns a hash of all keyword argument key-value pairs provided when the workflow was invoked.
|
|
224
|
+
# All keys are symbols and all values are strings.
|
|
225
|
+
#
|
|
226
|
+
# ### Invocation
|
|
227
|
+
# Invoke a workflow with keyword arguments like this:
|
|
228
|
+
# ```bash
|
|
229
|
+
# roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
|
|
230
|
+
# ```
|
|
231
|
+
#
|
|
232
|
+
# ### Usage
|
|
233
|
+
# ```ruby
|
|
234
|
+
# config do
|
|
235
|
+
# chat do
|
|
236
|
+
# model(kwargs[:model] || "gpt-5")
|
|
237
|
+
# temperature(kwargs[:temperature]&.to_f || 0.3)
|
|
238
|
+
# end
|
|
239
|
+
# end
|
|
240
|
+
# ```
|
|
241
|
+
#
|
|
242
|
+
# #### See Also
|
|
243
|
+
# - `kwarg` - Get a single keyword argument value
|
|
244
|
+
# - `kwarg!` - Get a required keyword argument value
|
|
245
|
+
# - `kwarg?` - Check if a keyword argument was provided
|
|
246
|
+
#
|
|
247
|
+
#: () -> Hash[Symbol, String]
|
|
248
|
+
def kwargs; end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -141,7 +141,7 @@ module Roast
|
|
|
141
141
|
#
|
|
142
142
|
# #### Configure the agent provider
|
|
143
143
|
# - `provider(symbol)` - Set the agent provider (e.g., `:claude`)
|
|
144
|
-
# - `use_default_provider!` - Use the default provider (`:
|
|
144
|
+
# - `use_default_provider!` - Use the default provider (`:pi`, or `$ROAST_DEFAULT_AGENT_PROVIDER` when set)
|
|
145
145
|
#
|
|
146
146
|
# #### Configure the base command used to run the coding agent
|
|
147
147
|
# - `command(string_or_array)` - Set the base command for invoking the agent
|
|
@@ -151,6 +151,10 @@ module Roast
|
|
|
151
151
|
# - `model(string)` - Set the model to use
|
|
152
152
|
# - `use_default_model!` - Use the provider's default model
|
|
153
153
|
#
|
|
154
|
+
# #### Configure a time limit
|
|
155
|
+
# - `timeout(seconds)` - Kill the agent and fail the cog if it runs longer than this
|
|
156
|
+
# - `no_timeout!` - Let the agent run for as long as it takes (default)
|
|
157
|
+
#
|
|
154
158
|
# #### Configure the system prompt
|
|
155
159
|
# - `replace_system_prompt(string)` - Completely replace the agent's default system prompt
|
|
156
160
|
# - `no_replace_system_prompt!` - Don't replace the default system prompt (default)
|
|
@@ -180,6 +184,10 @@ module Roast
|
|
|
180
184
|
# #### Configure debugging
|
|
181
185
|
# - `dump_raw_agent_messages_to(filename)` - Dump raw agent messages to a file for debugging
|
|
182
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
|
+
#
|
|
183
191
|
# #### Configure asynchronous execution
|
|
184
192
|
# - `async!` - Run the cog asynchronously in the background
|
|
185
193
|
# - `no_async!` (alias `sync!`) - Run the cog synchronously (default)
|
|
@@ -269,6 +277,10 @@ module Roast
|
|
|
269
277
|
# - `fail_on_error!` - Consider the cog failed if the command returns a non-zero exit status (default)
|
|
270
278
|
# - `no_fail_on_error!` - Don't fail the cog on non-zero exit status (exit status still available in output)
|
|
271
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
|
+
#
|
|
272
284
|
# #### Configure display output
|
|
273
285
|
# - `show_stdout!` - Write STDOUT to the console
|
|
274
286
|
# - `no_show_stdout!` - Don't write STDOUT to the console (default)
|