Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class and method comments on the relevant classes.
* [Additional Example Workflows]([https://github.com/Shopify/roast/tree/main/examples](https://github.com/Shopify/roast/tree/main/examples)) (these comprise the Roast end-to-end test suite)
* __Configuation__
* [General configuration block: `config-context.rbi`](https://github.com/Shopify/roast/blob/main/sorbet/rbi/shims/lib/roast/config_context.rbi)
* [Workflow params in cog config blocks: `cog/config.rbi`](https://github.com/Shopify/roast/blob/main/sorbet/rbi/shims/lib/roast/cog/config.rbi)
* [Global cog configuration: `cog/config.rb`](https://github.com/Shopify/roast/blob/main/lib/roast/cog/config.rb)
* [Agent cog configuration: `agent/config.rb`](https://github.com/Shopify/roast/blob/main/lib/roast/cogs/agent/config.rb)
* [Chat cog configuration: `chat/config.rb`](https://github.com/Shopify/roast/blob/main/lib/roast/cogs/chat/config.rb)
Expand Down
12 changes: 12 additions & 0 deletions examples/targets_and_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
#: self as Roast::Workflow

config do
# Workflow params are available inside cog config blocks and the `global` block, so you can
# configure cogs based on how the workflow was invoked.
chat do
# e.g., pick a cheaper model when the workflow is invoked with the `fast` flag:
# roast execute examples/targets_and_params.rb -- fast
model(arg?(:fast) ? "gpt-5.4-nano" : "gpt-5")
end

# `global` config applies to every cog; the same param accessors work here too.
global do
abort_on_failure! if arg?(:strict)
end
end

execute do
Expand Down
73 changes: 71 additions & 2 deletions lib/roast/config_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ class ConfigManagerNotPreparedError < ConfigManagerError; end
class ConfigManagerAlreadyPreparedError < ConfigManagerError; end
class IllegalCogNameError < ConfigManagerError; end

#: (Cog::Registry, Array[^() -> void]) -> void
def initialize(cog_registry, config_procs)
#: (Cog::Registry, Array[^() -> void], WorkflowContext) -> void
def initialize(cog_registry, config_procs, workflow_context)
@cog_registry = cog_registry
@config_procs = config_procs
@workflow_context = workflow_context
@config_context = ConfigContext.new #: ConfigContext
@global_config = Cog::Config.new #: Cog::Config
@general_configs = {} #: Hash[singleton(Cog), Cog::Config]
Expand Down Expand Up @@ -117,6 +118,7 @@ def on_config(cog_class, cog_name_or_pattern, cog_config_proc)
# NOTE: Sorbet expects the proc passed to instance_exec to be declared as taking an argument
# but our cog_config_proc does not get an argument
cog_config_proc = cog_config_proc #: as ^(untyped) -> void
bind_workflow_params(config_object)
config_object.instance_exec(&cog_config_proc) if cog_config_proc
nil
end
Expand All @@ -134,8 +136,75 @@ def bind_global
#: (^() -> void ) -> void
def on_global(global_config_proc)
global_config_proc = global_config_proc #: as ^(untyped) -> void
bind_workflow_params(@global_config)
@global_config.instance_exec(&global_config_proc) if global_config_proc
nil
end

#: (Cog::Config) -> void
def bind_workflow_params(object)
target_bang_method = method(:target!)
targets_method = method(:targets)
arg_question_method = method(:arg?)
args_method = method(:args)
kwarg_method = method(:kwarg)
kwarg_bang_method = method(:kwarg!)
kwarg_question_method = method(:kwarg?)
kwargs_method = method(:kwargs)
object.instance_eval do
define_singleton_method(:target!, proc { target_bang_method.call })
define_singleton_method(:targets, proc { targets_method.call })
define_singleton_method(:arg?, proc { |value| arg_question_method.call(value) })
define_singleton_method(:args, proc { args_method.call })
define_singleton_method(:kwarg, proc { |key| kwarg_method.call(key) })
define_singleton_method(:kwarg!, proc { |key| kwarg_bang_method.call(key) })
define_singleton_method(:kwarg?, proc { |key| kwarg_question_method.call(key) })
define_singleton_method(:kwargs, proc { kwargs_method.call })
end
end

#: () -> String
def target!
raise ArgumentError, "expected exactly one target" unless @workflow_context.params.targets.length == 1

@workflow_context.params.targets.first #: as String
end

#: () -> Array[String]
def targets
@workflow_context.params.targets.dup
end

#: (Symbol) -> bool
def arg?(value)
@workflow_context.params.args.include?(value)
end

#: () -> Array[Symbol]
def args
@workflow_context.params.args.dup
end

#: (Symbol) -> String?
def kwarg(key)
@workflow_context.params.kwargs[key]
end

#: (Symbol) -> String
def kwarg!(key)
raise ArgumentError, "expected keyword argument '#{key}' to be present" unless @workflow_context.params.kwargs.include?(key)

@workflow_context.params.kwargs[key] #: as String
end

#: (Symbol) -> bool
def kwarg?(key)
@workflow_context.params.kwargs.include?(key)
end

#: () -> Hash[Symbol, String]
def kwargs
@workflow_context.params.kwargs.dup
end
end
end
2 changes: 1 addition & 1 deletion lib/roast/workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def prepare!

@preparing = true
extract_dsl_procs!
@config_manager = ConfigManager.new(@cog_registry, @config_procs)
@config_manager = ConfigManager.new(@cog_registry, @config_procs, @workflow_context)
@config_manager.not_nil!.prepare!
# TODO: probably we should just not pass the params as the top-level scope value anymore
@execution_manager = ExecutionManager.new(@cog_registry, @config_manager.not_nil!, @execution_procs, @workflow_context, scope_value: @workflow_context.params)
Expand Down
251 changes: 251 additions & 0 deletions sorbet/rbi/shims/lib/roast/cog/config.rbi
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# typed: true
# frozen_string_literal: true

module Roast
class Cog
class Config
########################################
# Workflow Methods
########################################

# Get the single target value passed to the workflow
#
# Returns the target when exactly one target was provided to the workflow. Raises an
# `ArgumentError` if the workflow was invoked with zero or multiple targets.
#
# Targets are file paths, URLs, or other identifiers passed to the workflow when it is
# invoked. Use this method when your workflow expects exactly one target.
#
# ### Invocation
# Invoke a workflow with a single target like this:
# ```bash
# roast execute my_workflow.rb my_target_file.txt
# ```
#
# ### Usage
# ```ruby
# config do
# chat do
# # Configure based on the single target
# temperature(target!.end_with?(".md") ? 0.7 : 0.2)
# end
# end
# ```
#
# #### See Also
# - `targets` - Get all targets as an array (works with any number of targets)
#
#: () -> String
def target!; end

# Get all targets passed to the workflow
#
# Returns an array of all targets provided to the workflow. Works with any number of targets
# (zero, one, or many). Targets are file paths, URLs, or other identifiers passed when the
# workflow is invoked.
#
# ### Invocation
# Invoke a workflow with multiple targets like this:
# ```bash
# roast execute my_workflow.rb target_one.txt target_two.txt
# ```
# or using shell globs like this:
# ```bash
# roast execute my_workflow.rb target_*.txt
# ```
#
# ### Usage
# ```ruby
# config do
# map do
# # Process targets in parallel when there are several
# parallel! if targets.length > 3
# end
# end
# ```
#
# #### See Also
# - `target!` - Get the single target (raises an error if there isn't exactly one)
#
#: () -> Array[String]
def targets; end

# Check if a flag argument was passed to the workflow
#
# Returns `true` if the specified flag argument symbol was provided when the workflow
# was invoked, `false` otherwise.
#
# Flag arguments are symbolic flags passed to the workflow (e.g., `retry`, `force`)
# that enable or modify behavior.
#
# ### Invocation
# Invoke a workflow with flag arguments like this:
# ```bash
# roast execute my_workflow.rb [TARGETS] -- retry force
# ```
#
# ### Usage
# ```ruby
# config do
# chat do
# # Pick a cheaper model when invoked with the `fast` flag
# model(arg?(:fast) ? "gpt-5.4-nano" : "gpt-5")
# end
# end
# ```
#
# #### See Also
# - `args` - Get all flag arguments as an array
# - `kwarg?` - Check for keyword arguments (key-value pairs)
#
#: (Symbol) -> bool
def arg?(value); end

# Get all flag arguments passed to the workflow
#
# Returns an array of all flag argument symbols provided when the workflow was invoked.
# Flag arguments are symbolic flags (e.g., `retry`, `force`) that enable or modify
# workflow behavior.
#
# ### Invocation
# Invoke a workflow with flag arguments like this:
# ```bash
# roast execute my_workflow.rb [TARGETS] -- retry force
# ```
#
# ### Usage
# ```ruby
# config do
# global do
# # Show full output whenever any flags were passed
# display! unless args.empty?
# end
# end
# ```
#
# #### See Also
# - `arg?` - Check if a specific flag argument was provided
# - `kwargs` - Get all keyword arguments
#
#: () -> Array[Symbol]
def args; end

# Get a keyword argument value passed to the workflow
#
# Returns the string value for the specified keyword argument key, or `nil` if the key was
# not provided. Keyword arguments are key-value pairs passed to the workflow for configuration.
#
# ### Invocation
# Invoke a workflow with keyword arguments like this:
# ```bash
# roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
# ```
#
# ### Usage
# ```ruby
# config do
# chat do
# # Use a provided model override, or fall back to a default
# model(kwarg(:model) || "gpt-5")
# end
# end
# ```
#
# #### See Also
# - `kwarg!` - Get a keyword argument value (raises an error if not provided)
# - `kwarg?` - Check if a keyword argument was provided
# - `kwargs` - Get all keyword arguments as a hash
#
#: (Symbol) -> String?
def kwarg(key); end

# Get a required keyword argument value passed to the workflow
#
# Returns the string value for the specified keyword argument key. Raises an `ArgumentError`
# if the key was not provided.
#
# Use this when your workflow requires a specific keyword argument to function correctly.
#
# ### Invocation
# Invoke a workflow with keyword arguments like this:
# ```bash
# roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
# ```
#
# ### Usage
# ```ruby
# config do
# chat do
# # Require the 'model' keyword argument
# model(kwarg!(:model))
# end
# end
# ```
#
# #### See Also
# - `kwarg` - Get a keyword argument value (returns nil if not provided)
# - `kwarg?` - Check if a keyword argument was provided
# - `kwargs` - Get all keyword arguments as a hash
#
#: (Symbol) -> String
def kwarg!(key); end

# Check if a keyword argument was passed to the workflow
#
# Returns `true` if the specified keyword argument key was provided when the workflow was
# invoked, `false` otherwise.
#
# ### Invocation
# Invoke a workflow with keyword arguments like this:
# ```bash
# roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
# ```
#
# ### Usage
# ```ruby
# config do
# chat do
# temperature(0.9) if kwarg?(:creative)
# end
# end
# ```
#
# #### See Also
# - `kwarg` - Get a keyword argument value (returns nil if not provided)
# - `kwarg!` - Get a keyword argument value (raises an error if not provided)
# - `kwargs` - Get all keyword arguments as a hash
#
#: (Symbol) -> bool
def kwarg?(key); end

# Get all keyword arguments passed to the workflow
#
# Returns a hash of all keyword argument key-value pairs provided when the workflow was invoked.
# All keys are symbols and all values are strings.
#
# ### Invocation
# Invoke a workflow with keyword arguments like this:
# ```bash
# roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
# ```
#
# ### Usage
# ```ruby
# config do
# chat do
# model(kwargs[:model] || "gpt-5")
# temperature(kwargs[:temperature]&.to_f || 0.3)
# end
# end
# ```
#
# #### See Also
# - `kwarg` - Get a single keyword argument value
# - `kwarg!` - Get a required keyword argument value
# - `kwarg?` - Check if a keyword argument was provided
#
#: () -> Hash[Symbol, String]
def kwargs; end
end
end
end
Loading
Loading